Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial fetch timeout (with working tests) #4193

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3ccc3ba
jest config [nfc]: Use a new `projectForPlatform` instead of `common`.
chrisbobbe May 10, 2021
b9e5b0a
jest config [nfc]: Move `displayName`, `preset` to the top.
chrisbobbe May 10, 2021
fff1d37
fetchActions tests: Mock fetch response for fetchNewer/fetchOlder tests.
chrisbobbe May 11, 2021
7fd0b08
fetchActions tests: Use `reduxStatePlus`.
chrisbobbe May 11, 2021
8e897b9
jest: Fix facebook/jest#10221 locally.
chrisbobbe Oct 26, 2020
7bb6e3b
fetchActions tests: Remove a trivial test.
chrisbobbe May 19, 2021
c913d09
fetchActions tests [nfc]: Pull out some inline expressions to variables.
chrisbobbe May 19, 2021
2c5e614
fetchActions tests: Make the `tryFetchFunc`s more parallel.
chrisbobbe May 19, 2021
d02d78c
fetchActions tests: Use fake timers for `tryFetch`'s tests.
chrisbobbe May 19, 2021
1f4fd49
fetchActions tests: Test that `tryFetch` breaks retry loop on a 4xx.
chrisbobbe Jun 17, 2020
3c1596e
fetchActions tests: Test retry logic with a more representative error.
chrisbobbe May 19, 2021
b09d24a
async: Add `promiseTimeout`.
chrisbobbe Jun 18, 2020
d695252
apiErrors: Add `isServerError`.
chrisbobbe May 11, 2021
387dc53
fetchActions: Improve jsdoc.
chrisbobbe May 19, 2021
70cf1ad
redux: Add boilerplate for `INITIAL_FETCH_ABORT` action.
chrisbobbe Jun 17, 2020
d8818f6
fetchActions: Fail early on all non-5xx errors in `tryFetch`.
chrisbobbe May 19, 2021
cebac50
fetchActions: Solidify an assumption and add logging if it's violated.
chrisbobbe May 19, 2021
83bd132
fetchActions: Add a timeout to `tryFetch`.
chrisbobbe Jun 17, 2020
04a0507
fetchActions: Pull `await backoffMachine.wait()` out of `catch` block.
chrisbobbe Jun 23, 2020
b85b46b
fetchActions: Stop retrying on server errors in the initial fetch.
chrisbobbe May 19, 2021
7b408b5
fetchActions: Retry message fetch with a timeout.
chrisbobbe May 19, 2021
d812dd1
FetchError: Show a specific message if the error is a `TimeoutError`.
chrisbobbe May 19, 2021
2d59731
meta tests: Remove Lolex.
chrisbobbe Jul 14, 2020
c6095e2
queueMarkAsRead tests: Remove Lolex.
chrisbobbe Jul 14, 2020
81326ac
heartbeat tests: Remove Lolex.
chrisbobbe Jul 15, 2020
5e1726d
async tests: Remove Lolex.
chrisbobbe Jul 15, 2020
f6f6669
backoffMachine tests: Remove Lolex.
chrisbobbe Jul 15, 2020
6f592c7
tests: Remove our Lolex wrapper, uninstall Lolex.
chrisbobbe Jul 15, 2020
2fb95b3
jest: Make "modern" fake-timer implementation the default.
chrisbobbe Aug 25, 2020
8c20c8b
async tests [nfc]: Put BackoffMachine tests where they belong.
chrisbobbe Aug 25, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.yaml
Expand Up @@ -339,7 +339,7 @@ overrides:
#
# ================================================================
# Our test suite.
- files: ['**/__tests__/**']
- files: ['**/__tests__/**', 'jest/jestSetup.js']
rules:
no-restricted-imports: off

Expand Down
39 changes: 0 additions & 39 deletions flow-typed/npm/lolex_vx.x.x.js

This file was deleted.

80 changes: 53 additions & 27 deletions jest.config.js
Expand Up @@ -24,34 +24,60 @@ const transformModulesWhitelist = [
// (This value is correctly a string, not a RegExp.)
const transformIgnorePattern = `node_modules/(?!${transformModulesWhitelist.join('|')})`;

const common = {
// Finding and transforming source code.

testPathIgnorePatterns: ['/node_modules/', '/src/__tests__/lib/', '-testlib.js$'],

// When some source file foo.js says `import 'bar'`, Jest looks in the
// directories above foo.js for a directory like `node_modules` to find
// `bar` in. If foo.js is behind a `yarn link` symlink and outside our
// tree, that won't work; so have it look at our node_modules too.
moduleDirectories: ['node_modules', '<rootDir>/node_modules'],

transform: {
'^.+\\.js$': '<rootDir>/node_modules/react-native/jest/preprocessor.js',
},
transformIgnorePatterns: [transformIgnorePattern],

// The runtime test environment.
globals: {
__TEST__: true,
},
setupFiles: ['./jest/globalFetch.js', './node_modules/react-native-gesture-handler/jestSetup.js'],
setupFilesAfterEnv: ['./jest/jestSetup.js', 'jest-extended'],
const projectForPlatform = platform => {
if (!['ios', 'android'].includes(platform)) {
throw new Error(`Unsupported platform '${platform}'.`);
}
return {
displayName: platform,

// Ideally, these would simply be `jest-expo/ios` and
// `jest-expo/android`; see
// https://github.com/expo/expo/blob/master/packages/jest-expo/README.md#platforms.
// These custom presets are a workaround for a bug:
//
// `jest-expo`'s presets are based on `react-native`'s preset,
// which does something messy: it overwrites the global `Promise`.
// That's facebook/react-native#29303. Jest doesn't work well with
// that; that's facebook/jest#10221.
//
// So, until one of those is fixed, we use these custom presets to
// sandwich the code that replaces `global.Promise` with a fix:
//
// 1) save `global.Promise` to something else on `global`
// 2) let the `react-native` preset do its thing (like mocking RN
// libraries)
// 3) assign `global.Promise` back to what we saved in step 1
preset: platform === 'ios' ? './jest/presetIos' : './jest/presetAndroid',

timers: 'modern',

// Finding and transforming source code.
testPathIgnorePatterns: ['/node_modules/', '/src/__tests__/lib/', '-testlib.js$'],

// When some source file foo.js says `import 'bar'`, Jest looks in the
// directories above foo.js for a directory like `node_modules` to find
// `bar` in. If foo.js is behind a `yarn link` symlink and outside our
// tree, that won't work; so have it look at our node_modules too.
moduleDirectories: ['node_modules', '<rootDir>/node_modules'],

transform: {
'^.+\\.js$': '<rootDir>/node_modules/react-native/jest/preprocessor.js',
},
transformIgnorePatterns: [transformIgnorePattern],

// The runtime test environment.
globals: {
__TEST__: true,
},
setupFiles: [
'./jest/globalFetch.js',
'./node_modules/react-native-gesture-handler/jestSetup.js',
],
setupFilesAfterEnv: ['./jest/jestSetup.js', 'jest-extended'],
};
};

module.exports = {
// See https://github.com/expo/expo/blob/master/packages/jest-expo/README.md#platforms.
projects: [
{ ...common, displayName: 'ios', preset: 'jest-expo/ios' },
{ ...common, displayName: 'android', preset: 'jest-expo/android' },
],
projects: [projectForPlatform('ios'), projectForPlatform('android')],
};
7 changes: 7 additions & 0 deletions jest/jestSetup.js
Expand Up @@ -5,6 +5,8 @@ import { polyfillGlobal } from 'react-native/Libraries/Utilities/PolyfillFunctio
import { URL, URLSearchParams } from 'react-native-url-polyfill';
import mockAsyncStorage from '@react-native-community/async-storage/jest/async-storage-mock';

import { assertUsingModernFakeTimers } from '../src/__tests__/lib/fakeTimers';

// Use the same `URL` polyfill we do in the app.
//
// In the app we let `react-native-url-polyfill` handle doing this, by
Expand All @@ -18,6 +20,11 @@ import mockAsyncStorage from '@react-native-community/async-storage/jest/async-s
polyfillGlobal('URL', () => URL);
polyfillGlobal('URLSearchParams', () => URLSearchParams);

/**
* Default should be set with `timers: 'modern'` in Jest config.
*/
assertUsingModernFakeTimers();

// Mock `react-native` ourselves, following upstream advice [1] [2].
//
// Note that React Native's Jest setup (in their jest/setup.js)
Expand Down
14 changes: 14 additions & 0 deletions jest/presetAndroid.js
@@ -0,0 +1,14 @@
/**
* The preset we're making tweaks to.
*/
const basePreset = require('../node_modules/jest-expo/android/jest-preset.js');

// See comment in our Jest config about how this is used.
module.exports = {
...basePreset,
setupFiles: [
require.resolve('./savePromise.js'),
...basePreset.setupFiles,
require.resolve('./restorePromise.js'),
],
};
14 changes: 14 additions & 0 deletions jest/presetIos.js
@@ -0,0 +1,14 @@
/**
* The preset we're making tweaks to.
*/
const basePreset = require('../node_modules/jest-expo/ios/jest-preset.js');

// See comment in our Jest config about how this is used.
module.exports = {
...basePreset,
setupFiles: [
require.resolve('./savePromise.js'),
...basePreset.setupFiles,
require.resolve('./restorePromise.js'),
],
};
3 changes: 3 additions & 0 deletions jest/restorePromise.js
@@ -0,0 +1,3 @@
// For facebook/jest#10221. After savePromise.js and Jest's setup
// files have run, restore the natural value of `global.Promise`.
global.Promise = global.originalPromise;
4 changes: 4 additions & 0 deletions jest/savePromise.js
@@ -0,0 +1,4 @@
// For facebook/jest#10221. Before Jest's setup files have run, take
// note of what the natural value of `global.Promise` is, so we can
// restore it in restorePromise.js.
global.originalPromise = Promise;
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -119,7 +119,6 @@
"jest-expo": "^40.0.1",
"jest-extended": "^0.11.5",
"jetifier": "^1.6.5",
"lolex": "^5.1.1",
"metro-react-native-babel-preset": "^0.63.0",
"prettier": "^1.18.2",
"prettier-eslint": "^12.0.0",
Expand Down
29 changes: 26 additions & 3 deletions src/__tests__/lib/fakeTimers.js
@@ -1,14 +1,37 @@
/* @flow strict-local */
import { sleep } from '../../utils/async';

/**
* Ensure the "modern" fake-timer implementation is used.
*
* By setting `timers: 'modern'` in our Jest config, the modern
* implementation is the default. May be used to double-check that
* this default is in fact set, in our Jest setup file.
*
* Also, in one or two files, we switch over to using real timers,
* with `jest.useRealTimers()`. May be used in those files to make
* sure this setting doesn't linger where we don't want it to.
*/
export const assertUsingModernFakeTimers = () => {
// "Note: This function is only available when using modern fake
// timers implementation"
//
// -- https://jestjs.io/docs/en/jest-object#jestgetrealsystemtime
jest.getRealSystemTime();
};

/**
* Return a promise to sleep `ms` after advancing fake timers by `ms`.
*/
export const fakeSleep = async (ms: number): Promise<void> => {
// Only available if using the "modern" implementation
if (typeof jest.getRealSystemTime !== 'function') {
throw new Error("Tried to call `fakeSleep` without `jest.useFakeTimers('modern')` in effect.");
try {
assertUsingModernFakeTimers();
} catch (e) {
throw new Error(
'Tried to call `fakeSleep` without "modern" fake-timer implementation enabled.',
);
}

const sleepPromise = sleep(ms);
jest.advanceTimersByTime(ms);
return sleepPromise;
Expand Down
111 changes: 0 additions & 111 deletions src/__tests__/lib/lolex.js

This file was deleted.