Skip to content

Commit

Permalink
react: full hooks coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
elbywan committed Jun 9, 2019
1 parent 5074e44 commit 5bebdec
Show file tree
Hide file tree
Showing 16 changed files with 837 additions and 115 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Expand Up @@ -32,7 +32,7 @@
"camelcase": [
"error",
{
"properties": "always"
"properties": "never"
}
],
"comma-dangle": [
Expand Down
12 changes: 10 additions & 2 deletions package.json
Expand Up @@ -25,8 +25,7 @@
"build:react": "rollup -c config/rollup.react.config.js",
"build:classes": "rollup -c config/rollup.classes.config.js",
"build:websocket": "rollup -c config/rollup.websocket.config.js",
"build:test": "env TEST_BUILD=1 npm run build && npm run build:handlers && npm run build:classes && npm run build:websocket && npm run build:react",
"test": "npm run build:test && jest --coverage",
"test": "jest",
"coverage": "cat ./coverage/lcov.info | coveralls",
"clean": "rimraf {dist,react,handlers,websocket,classes}"
},
Expand All @@ -39,6 +38,15 @@
],
"author": "Julien Elbaz",
"license": "MIT",
"jest": {
"collectCoverage": true,
"collectCoverageFrom": [ "src/**/*.js"],
"coveragePathIgnorePatterns": [
"/node_modules/",
"/test/",
"/src/websocket/browser.js"
]
},
"devDependencies": {
"@babel/preset-env": "^7.4.5",
"@babel/preset-react": "^7.0.0",
Expand Down
28 changes: 12 additions & 16 deletions src/react/hooks/tools.js
@@ -1,7 +1,18 @@
export const defaultRootKey = '__requests__'
export const defaultSerialize = (method, url) => `${method}@${url}`
export const identity = _ => _

export const normalizedOperations = {
read(mappings, store) {
const storeFragment = {}
Object.entries(mappings).forEach(([ entity, ids ]) => {
storeFragment[entity] = {}
ids.forEach(key => {
storeFragment[entity][key] = store[entity] && store[entity][key] || null
})
})
return storeFragment
},
write(normalizedData, store) {
Object.entries(normalizedData).forEach(([ entity, entityData ]) => {
if(!store[entity]) {
Expand All @@ -10,11 +21,7 @@ export const normalizedOperations = {

Object.entries(entityData).forEach(([ key, value ]) => {
if(store[entity][key]) {
if(Array.isArray(store[entity][key]) && Array.isArray(value)) {
value.forEach(item => {
store[entity][key].push(item)
})
} else if(typeof store[entity][key] === 'object' && typeof value === 'object') {
if(typeof store[entity][key] === 'object' && typeof value === 'object') {
Object.entries(value).forEach(([k, v]) => {
store[entity][key][k] = v
})
Expand All @@ -26,16 +33,5 @@ export const normalizedOperations = {
}
})
})
},
read(mappings, store) {
const storeFragment = {}
Object.entries(mappings).forEach(([ entity, ids ]) => {
if(!storeFragment[entity])
storeFragment[entity] = {}
ids.forEach(key => {
storeFragment[entity][key] = store[entity][key]
})
})
return storeFragment
}
}
9 changes: 7 additions & 2 deletions src/react/hooks/useNormalizedRequest.js
Expand Up @@ -89,13 +89,18 @@ export function useNormalizedRequest(url, {

useEffect(function() {
checkAndRefetch()
}, [ storeKey ])
}, [ storeKey, skip() ])

if(ssrContext) {
checkAndRefetch(true)
}

return skip() ? {} : {
return skip() ? {
data: null,
error: null,
loading: false,
refetch
} : {
loading,
data,
error,
Expand Down
9 changes: 7 additions & 2 deletions src/react/hooks/useRequest.js
Expand Up @@ -78,13 +78,18 @@ export function useRequest(url, {

useEffect(function() {
checkAndRefetch()
}, [ storeKey ])
}, [ storeKey, skip() ])

if(ssrContext) {
checkAndRefetch(true)
}

return skip() ? {} : {
return skip() ? {
data: null,
error: null,
loading: false,
refetch
} : {
loading,
data,
error,
Expand Down
6 changes: 3 additions & 3 deletions src/react/hooks/useResource.js
Expand Up @@ -5,15 +5,15 @@ import { HyperactivContext } from '../context/index'
function formatData(data, entity, id) {
return (
data ?
id ?
id !== null ?
data[entity] && data[entity][id] :
data[entity] && Object.values(data[entity]) :
data
)
}

export function useResource(entity, url, {
id,
id = null,
store,
normalize,
client,
Expand Down Expand Up @@ -60,7 +60,7 @@ export function useResource(entity, url, {
formatData(data, entity, id)
, [data, entity, id])

const refetch = normalizedRefetch.then(data =>
const refetch = () => normalizedRefetch().then(data =>
formatData(data, entity, id)
)

Expand Down
2 changes: 1 addition & 1 deletion src/react/index.js
Expand Up @@ -6,5 +6,5 @@ export * from './hooks/index'
export * from './context/index'

export const store = function(obj, options = {}) {
return hyperactiv.observe(obj, Object.assign({ deep: true, batch: false }, options))
return hyperactiv.observe(obj, Object.assign({ deep: true, batch: true }, options))
}
2 changes: 1 addition & 1 deletion test/classes.test.js
@@ -1,4 +1,4 @@
const classes = require('../classes/classes.js')
const classes = require('../src/classes')
const { Observable } = classes

test('onChange should catch mutation', done => {
Expand Down
4 changes: 2 additions & 2 deletions test/handlers.test.js
@@ -1,5 +1,5 @@
const hyperactiv = require('../dist/hyperactiv.js')
const handlers = require('../handlers/handlers.js')
const hyperactiv = require('../src/index').default
const handlers = require('../src/handlers').default
const { observe } = hyperactiv
const { all, write, debug } = handlers

Expand Down
2 changes: 1 addition & 1 deletion test/index.test.js
@@ -1,4 +1,4 @@
const hyperactiv = require('../dist/hyperactiv.js')
const hyperactiv = require('../src/index').default
const { computed, observe, dispose } = hyperactiv

const delay = time => new Promise(resolve => setTimeout(resolve, time))
Expand Down
3 changes: 3 additions & 0 deletions test/react/__snapshots__/context.test.js.snap
Expand Up @@ -5,12 +5,15 @@ exports[`React context test suite SSR Provider and preloadData should resolve pr
hello world
bonjour le monde
1
</div>
`;

exports[`React context test suite preloadData should resolve promises based on its depth option 1`] = `
<div>
hello world
</div>
`;
156 changes: 80 additions & 76 deletions test/react/components.test.js
Expand Up @@ -12,8 +12,10 @@ import {
watch,
store as createStore,
HyperactivProvider
} from '../../react/react.js'
} from '../../src/react'
import { ignoreActErrors } from './utils'

ignoreActErrors()
afterEach(cleanup)

describe('React components test suite', () => {
Expand Down Expand Up @@ -61,95 +63,97 @@ describe('React components test suite', () => {
})
}

/* watch() */
describe('watch()', () => {

test('watch() should observe a class component', () => {
const store = createStore({
firstName: 'Igor',
lastName: 'Gonzola'
})
const ClassComponent = watch(class extends React.Component {
render() {
return commonJsx(store)
}
})

return testStoreUpdate(ClassComponent, store)
})
it('should observe a class component', () => {
const store = createStore({
firstName: 'Igor',
lastName: 'Gonzola'
})
const ClassComponent = watch(class extends React.Component {
render() {
return commonJsx(store)
}
})

test('watch() should observe a functional component', () => {
const store = createStore({
firstName: 'Igor',
lastName: 'Gonzola'
return testStoreUpdate(ClassComponent, store)
})
const FunctionalComponent = watch(() =>
commonJsx(store)
)

return testStoreUpdate(FunctionalComponent, store)
})
it('should observe a functional component', () => {
const store = createStore({
firstName: 'Igor',
lastName: 'Gonzola'
})
const FunctionalComponent = watch(() =>
commonJsx(store)
)

test('watch() wrapping a functional component should inject the `store` prop', () => {
const store = createStore({
hello: 'World'
return testStoreUpdate(FunctionalComponent, store)
})
const Wrapper = watch(props => <div data-testid="hello-div">{props.store && props.store.hello}</div>)
const { getByTestId } = render(
<Wrapper />
)
expect(getByTestId('hello-div')).toContainHTML('')
const { getByText } = render(
<HyperactivProvider store={store}>

test('wrapping a functional component should inject the `store` prop', () => {
const store = createStore({
hello: 'World'
})
const Wrapper = watch(props => <div data-testid="hello-div">{props.store && props.store.hello}</div>)
const { getByTestId } = render(
<Wrapper />
</HyperactivProvider>
)
expect(getByText('World')).toBeTruthy()
})
)
expect(getByTestId('hello-div')).toContainHTML('')
const { getByText } = render(
<HyperactivProvider store={store}>
<Wrapper />
</HyperactivProvider>
)
expect(getByText('World')).toBeTruthy()
})

test('watch() wrapping a functional component should not inject the `store` prop if a prop with this name already exists', () => {
const store = createStore({
hello: 'World'
test('wrapping a functional component should not inject the `store` prop if a prop with this name already exists', () => {
const store = createStore({
hello: 'World'
})
const Wrapper = watch(props => <div data-testid="hello-div">{props.store && props.store.hello}</div>)
const { getByTestId } = render(
<HyperactivProvider store={store}>
<Wrapper store={{ hello: 'bonjour' }}/>
</HyperactivProvider>
)
expect(getByTestId('hello-div')).toHaveTextContent('bonjour')
})
const Wrapper = watch(props => <div data-testid="hello-div">{props.store && props.store.hello}</div>)
const { getByTestId } = render(
<HyperactivProvider store={store}>
<Wrapper store={{ hello: 'bonjour' }}/>
</HyperactivProvider>
)
expect(getByTestId('hello-div')).toHaveTextContent('bonjour')
})

test('watch() wrapping a class component should gracefully unmount if the child component has a componentWillUnmount method', () => {
let unmounted = false
const Wrapper = watch(class extends React.Component {
componentWillUnmount() {
unmounted = true
}
render() {
return <div>Hello</div>
}
test('wrapping a class component should gracefully unmount if the child component has a componentWillUnmount method', () => {
let unmounted = false
const Wrapper = watch(class extends React.Component {
componentWillUnmount() {
unmounted = true
}
render() {
return <div>Hello</div>
}
})
const { getByText, unmount } = render(<Wrapper />)
expect(unmounted).toBe(false)
expect(getByText('Hello')).toBeTruthy()
unmount()
expect(unmounted).toBe(true)
})
const { getByText, unmount } = render(<Wrapper />)
expect(unmounted).toBe(false)
expect(getByText('Hello')).toBeTruthy()
unmount()
expect(unmounted).toBe(true)
})

/* <Watch /> */
describe('<Watch />', () => {
it('should observe its render function', () => {
const store = createStore({
firstName: 'Igor',
lastName: 'Gonzola'
})
const ComponentWithWatch = () =>
<Watch render={() => commonJsx(store)} />

test('<Watch /> should observe its render function', () => {
const store = createStore({
firstName: 'Igor',
lastName: 'Gonzola'
return testStoreUpdate(ComponentWithWatch, store)
})
it('should not render anything if no render prop is passed', () => {
const { container } = render(<Watch />)
expect(container).toContainHTML('')
})
const ComponentWithWatch = () =>
<Watch render={() => commonJsx(store)} />

return testStoreUpdate(ComponentWithWatch, store)
})
test('<Watch /> should not render anything if no render prop is passed', () => {
const { container } = render(<Watch />)
expect(container).toContainHTML('')
})

})

0 comments on commit 5bebdec

Please sign in to comment.