-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added infinite scroll async collection bundle
- Loading branch information
1 parent
19e5899
commit 822af68
Showing
19 changed files
with
912 additions
and
360 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
export default { | ||
name: 'paging', | ||
reducer: (state = { currentPage: null, pageSize: 10 }, action) => { | ||
if (action.type === 'paging.change') { | ||
return { | ||
...state, | ||
...action.payload, | ||
} | ||
} | ||
return state | ||
}, | ||
doChangePaging: payload => ({ type: 'paging.change', payload: payload }), | ||
selectCurrentPage: state => state.paging.currentPage, | ||
selectPageSize: state => state.paging.pageSize, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import timekeeper from 'timekeeper' | ||
import { tapReactors } from './utils' | ||
|
||
export const START_TIME = 1000 | ||
|
||
export function setUpTimeTravel() { | ||
beforeEach(() => timekeeper.freeze(START_TIME)) | ||
|
||
afterEach(() => timekeeper.reset()) | ||
} | ||
|
||
export function timeTravelTo(time, store) { | ||
return new Promise(async (resolve, reject) => { | ||
try { | ||
timekeeper.travel(START_TIME + time) | ||
store.dispatch({ type: 'dummy action', payload: null }) // just tap the app time | ||
await tapReactors() | ||
resolve() | ||
} catch (e) { | ||
reject(e) | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export async function tapReactors() { | ||
const tapOneReactor = () => new Promise(resolve => setTimeout(resolve, 0)) | ||
await tapOneReactor() | ||
await tapOneReactor() | ||
await tapOneReactor() | ||
} |
105 changes: 105 additions & 0 deletions
105
src/__test_behaviors__/behavesAsResourceWithDependencies.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { tapReactors } from '../__mocks__/utils' | ||
|
||
export default function behavesAsStalingResource( | ||
{ selectors, actionCreators }, | ||
{ fetchActionCreator, fetchPendingSelector, createStore } | ||
) { | ||
const isPendingForFetch = store => store[fetchPendingSelector]() | ||
const isStale = store => store[selectors.isStale]() | ||
const isPresent = store => store[selectors.isPresent]() | ||
const triggerFetch = store => store[fetchActionCreator]() | ||
|
||
describe('dependency keys', () => { | ||
describe('with one dependency', () => { | ||
test('does not try to fetch while dependencies are not satisfied', () => { | ||
const { store } = createStore({ dependencyKey: 'currentPage' }) | ||
expect(isPendingForFetch(store)).toBe(false) | ||
}) | ||
|
||
test('triggers pending state as soon as dependency is resolved', async () => { | ||
const { store } = createStore({ dependencyKey: 'currentPage' }) | ||
store.doChangePaging({ currentPage: 1 }) | ||
await tapReactors() | ||
expect(isPendingForFetch(store)).toBe(true) | ||
}) | ||
|
||
test('clears the store when dependency changes', async () => { | ||
const { store, apiMock } = createStore({ dependencyKey: 'currentPage' }) | ||
store.doChangePaging({ currentPage: 1 }) | ||
await tapReactors() | ||
triggerFetch(store) | ||
await apiMock.resolveAllFetchRequests() | ||
|
||
store.doChangePaging({ currentPage: 2 }) | ||
await tapReactors() | ||
expect(isPresent(store)).toBe(false) | ||
}) | ||
|
||
test('stales a resource instead of clearing it with "stale" option', async () => { | ||
const { store, apiMock } = createStore({ dependencyKey: { key: 'currentPage', staleOnChange: true } }) | ||
store.doChangePaging({ currentPage: 1 }) | ||
await tapReactors() | ||
triggerFetch(store) | ||
await apiMock.resolveAllFetchRequests() | ||
|
||
store.doChangePaging({ currentPage: 2 }) | ||
await tapReactors() | ||
|
||
expect(isPresent(store)).toBe(true) | ||
expect(isStale(store)).toBe(true) | ||
}) | ||
|
||
test('allows to enable passing down blank values', async () => { | ||
const { store } = createStore({ dependencyKey: { key: 'currentPage', allowBlank: true } }) | ||
await tapReactors() | ||
expect(isPendingForFetch(store)).toBe(true) | ||
}) | ||
|
||
test.todo('mixes in dependency values into getPromise call') | ||
}) | ||
|
||
describe('with multiple dependencies', () => { | ||
test('does not try to fetch while dependencies are not satisfied', () => { | ||
const { store } = createStore({ dependencyKey: ['currentPage', 'pageSize'] }) | ||
expect(isPendingForFetch(store)).toBe(false) | ||
}) | ||
|
||
test('triggers pending state as soon as dependency is resolved', async () => { | ||
const { store } = createStore({ dependencyKey: ['currentPage', 'pageSize'] }) | ||
store.doChangePaging({ currentPage: 1 }) | ||
await tapReactors() | ||
await tapReactors() | ||
expect(isPendingForFetch(store)).toBe(true) | ||
}) | ||
|
||
test('clears the store when dependency changes', async () => { | ||
const { store, apiMock } = createStore({ dependencyKey: ['currentPage', 'pageSize'] }) | ||
store.doChangePaging({ currentPage: 1 }) | ||
await tapReactors() | ||
await tapReactors() | ||
|
||
triggerFetch(store) | ||
await apiMock.resolveAllFetchRequests() | ||
|
||
store.doChangePaging({ currentPage: 2 }) | ||
await tapReactors() | ||
expect(isPresent(store)).toBe(false) | ||
}) | ||
|
||
test('stales a resource instead of clearing it with "stale" option and allows blank items', async () => { | ||
const { store } = createStore({ | ||
dependencyKey: [{ key: 'currentPage', allowBlank: true }, { key: 'pageSize', staleOnChange: true }], | ||
}) | ||
|
||
await tapReactors() | ||
expect(isPendingForFetch(store)).toBe(true) | ||
|
||
store.doChangePaging({ pageSize: 120 }) | ||
await tapReactors() | ||
expect(isStale(store)).toBe(true) | ||
}) | ||
|
||
test.todo('mixes in dependency values into getPromise call') | ||
}) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { timeTravelTo } from '../__mocks__/time' | ||
|
||
export default function behavesAsStalingResource({ selectors, actionCreators }, { fetchActionCreator, createStore }) { | ||
describe('staling feature', () => { | ||
test('marks item as stale manually', async () => { | ||
const { store, apiMock } = createStore({ staleAfter: 15 }) | ||
|
||
store[fetchActionCreator]() | ||
|
||
await apiMock.resolveAllFetchRequests() | ||
|
||
store[actionCreators.doMarkAsStale]() | ||
|
||
expect(store[selectors.isPresent]()).toBe(true) | ||
expect(store[selectors.isStale]()).toBe(true) | ||
|
||
await timeTravelTo(16, store) | ||
|
||
expect(store[selectors.isPresent]()).toBe(true) | ||
expect(store[selectors.isStale]()).toBe(true) | ||
}) | ||
|
||
test('marks item as stale with a timer', async () => { | ||
const { store, apiMock } = createStore({ staleAfter: 15 }) | ||
|
||
store[fetchActionCreator]() | ||
|
||
await apiMock.resolveAllFetchRequests() | ||
|
||
expect(store[selectors.isStale]()).toBe(false) | ||
|
||
await timeTravelTo(16, store) | ||
|
||
expect(store[selectors.isStale]()).toBe(true) | ||
}) | ||
}) | ||
} |
Oops, something went wrong.