Repository: reduxjs/redux-toolkit
Pull request #575 (2020-05-21 05:33:47)

docs: implement dark mode
Picking up from #247, I've implemented changes for the following:

In dark mode:
- inline code
- inline code with links

In light mode:
- inline code with links

There was also a warning with the `aria-hidden` tag on the landing page that has been fixed.

Attaching a screenshot to highlight the changes for the dark mode below; I had tweak the shade of purple being used for the `code a` elements. There were infima-based vars available in the `custom.css` file, so I went ahead and used those.

Since accessibility was one of the reasons that previous attempts didn't go through, I tried to make sure that there was enough contrast with the shades chosen (purple isn't easy to implement dark mode with 😅). Here's the results:


Before:
| Mode  | Element | Contrast |
| ----- | ------- | -------- |
| Light | code a  | 4.43     |
| Dark  | code    | 5.26     |
| Dark  | code a  | 1.06     |

After:
| Mode  | Element | Variation        | Contrast |
| ----- | ------- | ---------------- | -------- |
| Light | code a  |                  | 5.15     |
| Dark  | code    |                  | 8.38     |
| Dark  | code a  | inherited color  | 2.77     |
| Dark  | code a  | "light" purple   | 3.33     |
| Dark  | code a  | "lighter" purple | 4.87     |

Would love to see dark mode getting finally enabled for the docs, please feel free to make any tweaks/suggestions that you'd like.



<img width="1440" alt="image" src="https://user-images.githubusercontent.com/11270438/82525437-03669e80-9b4f-11ea-957c-bd962ce3e824.png">


netlify[bot]: Deploy preview for *redux-starter-kit-docs* ready!

Built with commit aa541fd0485873d2f95510c50ea547001d64e68b

https://deploy-preview-575--redux-starter-kit-docs.netlify.app
codesandbox[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/reduxjs/redux-toolkit/pr/575/builds/27830) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit aa541fd0485873d2f95510c50ea547001d64e68b:

|Sandbox| Source |
|--|--|
|[modest-shtern-yr6pk](https://codesandbox.io/s/modest-shtern-yr6pk)| Configuration |
|[clever-ritchie-85kv4](https://codesandbox.io/s/clever-ritchie-85kv4)| Configuration |
|[hidden-dew-jfnem](https://codesandbox.io/s/hidden-dew-jfnem)| Configuration |


Patch:
diff --git a/docs/api/createAsyncThunk.md b/docs/api/createAsyncThunk.md
index 42e72243..08a8202f 100644
--- a/docs/api/createAsyncThunk.md
+++ b/docs/api/createAsyncThunk.md
@@ -66,7 +66,7 @@ For example, a `type` argument of `'users/requestStatus'` will generate these ac
 
 ### `payloadCreator`
 
-A callback function that should return a promise containing the result of some asynchronous logic. It may also return a value synchronously. If there is an error, it should either return a rejected promise containing an `Error` instance or a plain value such as a descriptive error message or otherwise a resolved promise with a `RejectWithValue` argument as returned by the `thunkApi.rejectWithValue` function.
+A callback function that should return a promise containing the result of some asynchronous logic. It may also return a value synchronously. If there is an error, it should either return a rejected promise containing an `Error` instance or a plain value such as a descriptive error message or otherwise a resolved promise with a `RejectWithValue` argument as returned by the `thunkAPI.rejectWithValue` function.
 
 The `payloadCreator` function can contain whatever logic you need to calculate an appropriate result. This could include a standard AJAX data fetch request, multiple AJAX calls with the results combined into a final value, interactions with React Native `AsyncStorage`, and so on.
 
@@ -295,7 +295,7 @@ function MyComponent(props: { userId: string }) {
 
 After a thunk has been cancelled this way, it will dispatch (and return) a `"thunkName/rejected"` action with an `AbortError` on the `error` property. The thunk will not dispatch any further actions.
 
-Additionally, your `payloadCreator` can use the `AbortSignal` it is passed via `thunkApi.signal` to actually cancel a costly asynchronous action.
+Additionally, your `payloadCreator` can use the `AbortSignal` it is passed via `thunkAPI.signal` to actually cancel a costly asynchronous action.
 
 The `fetch` api of modern browsers already comes with support for an `AbortSignal`:
 
@@ -481,7 +481,7 @@ const usersSlice = createSlice({
 })
 
 const UsersComponent = () => {
-  const { users, loading, error } = useSelector(state => state.users)
+  const { entities, error } = useSelector(state => state.users)
   const dispatch = useDispatch()
 
   // This is an example of an onSubmit handler using Formik meant to demonstrate accessing the payload of the rejected action
@@ -582,9 +582,7 @@ const usersSlice = createSlice({
 })
 
 const UsersComponent = () => {
-  const { users, loading, error } = useSelector(
-    (state: RootState) => state.users
-  )
+  const { entities, error } = useSelector((state: RootState) => state.users)
   const dispatch: AppDispatch = useDispatch()
 
   // This is an example of an onSubmit handler using Formik meant to demonstrate accessing the payload of the rejected action
diff --git a/docs/api/getDefaultMiddleware.md b/docs/api/getDefaultMiddleware.md
index bc9928a3..d5978285 100644
--- a/docs/api/getDefaultMiddleware.md
+++ b/docs/api/getDefaultMiddleware.md
@@ -41,12 +41,29 @@ middleware added as well:
 ```js
 const store = configureStore({
   reducer: rootReducer,
-  middleware: [...getDefaultMiddleware(), logger]
+  middleware: getDefaultMiddleware().concat(logger)
 })
 
 // Store has all of the default middleware added, _plus_ the logger middleware
 ```
 
+## Usage without import (middleware callback notation)
+
+For convenience, the `middleware` property of `configureStore` can be used with a callback notation like this.
+
+```js
+const store = configureStore({
+  reducer: rootReducer,
+  middleware: getDefaultMiddleware => getDefaultMiddleware().concat(logger)
+})
+
+// Store has all of the default middleware added, _plus_ the logger middleware
+```
+
+While this does not make much of a difference for JavaScript users, using this notation is preferrable to TypeScript users, as that version of `getDefaultMiddleware` is already correctly pre-typed for the `Store`'s type, so no use of generics is necessary.
+
+Also, when using TypeScript, it is preferrable to use the chainable `.concat(...)` and `.prepend(...)` methods of the returned `MiddlewareArray` instead of the array spread operator, as the latter can lose valuable type information under some circumstances.
+
 ## Included Default Middleware
 
 ### Development
diff --git a/docs/tutorials/basic-tutorial.md b/docs/tutorials/basic-tutorial.md
index 0126470e..cb6c734a 100644
--- a/docs/tutorials/basic-tutorial.md
+++ b/docs/tutorials/basic-tutorial.md
@@ -157,7 +157,9 @@ function counter(state = 0, action) {
   }
 }
 
-const store = Redux.createStore(counter)
+const store = configureStore({
+  reducer: counter
+})
 
 document.getElementById('increment').addEventListener('click', () => {
   store.dispatch(increment())
diff --git a/docs/usage/usage-with-typescript.md b/docs/usage/usage-with-typescript.md
index ab1a5d46..b3320486 100644
--- a/docs/usage/usage-with-typescript.md
+++ b/docs/usage/usage-with-typescript.md
@@ -55,20 +55,21 @@ const store = configureStore({
 })
 
 export type AppDispatch = typeof store.dispatch
-export const useAppDispatch = () => useDispatch<AppDispatch>() // Export a hook that can be resused to resolve types
+export const useAppDispatch = () => useDispatch<AppDispatch>() // Export a hook that can be reused to resolve types
 ```
 
 ### Correct typings for the `Dispatch` type
 
 The type of the `dispatch` function type will be directly inferred from the `middleware` option. So if you add _correctly typed_ middlewares, `dispatch` should already be correctly typed.
 
-There might however be cases, where TypeScript decides to simplify your provided middleware array down to just `Array<Middleware>`. In that case, you have to either specify the array type manually as a tuple, or in TS versions >= 3.4, just add `as const` to your definition.
+As TypeScript often widens array types when combining arrays using the spread operator, we suggest using the `.concat(...)` and `.prepend(...)` methods of the `MiddlewareArray` returned by `getDefaultMiddleware()`.
 
-Please note that when calling `getDefaultMiddleware` in TypeScript, you have to provide the state type as a generic argument.
+Also, we suggest using the callback notation for the `middleware` option go get a correctly pre-typed version of `getDefaultMiddleware` that does not require you to specify any generics by hand.
 
 ```ts {10-20}
 import { configureStore } from '@reduxjs/toolkit'
 import additionalMiddleware from 'additional-middleware'
+import logger from 'redux-logger'
 // @ts-ignore
 import untypedMiddleware from 'untyped-middleware'
 import rootReducer from './rootReducer'
@@ -76,23 +77,45 @@ import rootReducer from './rootReducer'
 type RootState = ReturnType<typeof rootReducer>
 const store = configureStore({
   reducer: rootReducer,
-  middleware: [
-    // getDefaultMiddleware needs to be called with the state type
-    ...getDefaultMiddleware<RootState>(),
-    // correctly typed middlewares can just be used
-    additionalMiddleware,
-    // you can also manually type middlewares manually
-    untypedMiddleware as Middleware<
-      (action: Action<'specialAction'>) => number,
-      RootState
-    >
-  ] as const // prevent this from becoming just `Array<Middleware>`
+  middleware: getDefaultMiddleware =>
+    getDefaultMiddleware()
+      .prepend(
+        // correctly typed middlewares can just be used
+        additionalMiddleware,
+        // you can also manually type middlewares manually
+        untypedMiddleware as Middleware<
+          (action: Action<'specialAction'>) => number,
+          RootState
+        >
+      )
+      // prepend and concat calls can be chained
+      .concat(logger)
 })
 
 type AppDispatch = typeof store.dispatch
 ```
 
-If you need any additional reference or examples, [the type tests for `configureStore`](https://github.com/reduxjs/redux-toolkit/blob/master/type-tests/files/configureStore.typetest.ts) contain many different scenarios on how to type this.
+#### Using `MiddlewareArray` without `getDefaultMiddleware`
+
+If you want to skip the usage of `getDefaultMiddleware` altogether, you can still use `MiddlewareArray` for type-safe concatenation of your `middleware` array. This class extends the default JavaScript `Array` type, only with modified typings for `.concat(...)` and the additional `.prepend(...)` method.
+
+This is generally not required though, as you will probably not run into any array-type-widening issues as long as you are using `as const` and do not use the spread operator.
+
+So the following two calls would be equivalent:
+
+```ts
+import { configureStore, MiddlewareArray } from '@reduxjs/toolkit'
+
+configureStore({
+  reducer: rootReducer,
+  middleware: new MiddlewareArray().concat(additionalMiddleware, logger)
+})
+
+configureStore({
+  reducer: rootReducer,
+  middleware: [additionalMiddleware, logger] as const
+})
+```
 
 ### Using the extracted `Dispatch` type with React-Redux
 
diff --git a/etc/redux-toolkit.api.md b/etc/redux-toolkit.api.md
index 401db656..005aa3da 100644
--- a/etc/redux-toolkit.api.md
+++ b/etc/redux-toolkit.api.md
@@ -59,6 +59,14 @@ export interface ActionReducerMapBuilder<State> {
 // @public @deprecated
 export type Actions<T extends keyof any = string> = Record<T, Action>;
 
+// @public
+export type AsyncThunk<Returned, ThunkArg, ThunkApiConfig extends AsyncThunkConfig> = AsyncThunkActionCreator<Returned, ThunkArg, ThunkApiConfig> & {
+    pending: AsyncThunkPendingActionCreator<ThunkArg>;
+    rejected: AsyncThunkRejectedActionCreator<ThunkArg, ThunkApiConfig>;
+    fulfilled: AsyncThunkFulfilledActionCreator<Returned, ThunkArg>;
+    typePrefix: string;
+};
+
 // @public
 export type AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig extends AsyncThunkConfig> = (dispatch: GetDispatch<ThunkApiConfig>, getState: () => GetState<ThunkApiConfig>, extra: GetExtra<ThunkApiConfig>) => Promise<PayloadAction<Returned, string, {
     arg: ThunkArg;
@@ -112,7 +120,7 @@ export function configureStore<S = any, A extends Action = AnyAction, M extends
 export interface ConfigureStoreOptions<S = any, A extends Action = AnyAction, M extends Middlewares<S> = Middlewares<S>> {
     devTools?: boolean | EnhancerOptions;
     enhancers?: StoreEnhancer[] | ConfigureEnhancersCallback;
-    middleware?: M;
+    middleware?: ((getDefaultMiddleware: CurriedGetDefaultMiddleware<S>) => M) | M;
     preloadedState?: DeepPartial<S extends any ? S : S>;
     reducer: Reducer<S, A> | ReducersMapObject<S, A>;
 }
@@ -124,23 +132,7 @@ export function createAction<P = void, T extends string = string>(type: T): Payl
 export function createAction<PA extends PrepareAction<any>, T extends string = string>(type: T, prepareAction: PA): PayloadActionCreator<ReturnType<PA>['payload'], T, PA>;
 
 // @public (undocumented)
-export function createAsyncThunk<Returned, ThunkArg = void, ThunkApiConfig extends AsyncThunkConfig = {}>(typePrefix: string, payloadCreator: (arg: ThunkArg, thunkAPI: GetThunkAPI<ThunkApiConfig>) => Promise<Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>> | Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>, options?: AsyncThunkOptions<ThunkArg, ThunkApiConfig>): IsAny<ThunkArg, (arg: ThunkArg) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig>, unknown extends ThunkArg ? (arg: ThunkArg) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> : [ThunkArg] extends [void] | [undefined] ? () => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> : [void] extends [ThunkArg] ? (arg?: ThunkArg | undefined) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> : [undefined] extends [ThunkArg] ? (arg?: ThunkArg | undefined) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig> : (arg: ThunkArg) => AsyncThunkAction<Returned, ThunkArg, ThunkApiConfig>> & {
-    pending: ActionCreatorWithPreparedPayload<[string, ThunkArg], undefined, string, never, {
-        arg: ThunkArg;
-        requestId: string;
-    }>;
-    rejected: ActionCreatorWithPreparedPayload<[Error | null, string, ThunkArg, (GetRejectValue<ThunkApiConfig> | undefined)?], GetRejectValue<ThunkApiConfig> | undefined, string, SerializedError, {
-        arg: ThunkArg;
-        requestId: string;
-        aborted: boolean;
-        condition: boolean;
-    }>;
-    fulfilled: ActionCreatorWithPreparedPayload<[Returned, string, ThunkArg], Returned, string, never, {
-        arg: ThunkArg;
-        requestId: string;
-    }>;
-    typePrefix: string;
-};
+export function createAsyncThunk<Returned, ThunkArg = void, ThunkApiConfig extends AsyncThunkConfig = {}>(typePrefix: string, payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, ThunkApiConfig>, options?: AsyncThunkOptions<ThunkArg, ThunkApiConfig>): AsyncThunk<Returned, ThunkArg, ThunkApiConfig>;
 
 // @public (undocumented)
 export function createEntityAdapter<T>(options?: {
@@ -279,7 +271,7 @@ export function getDefaultMiddleware<S = any, O extends Partial<GetDefaultMiddle
     thunk: true;
     immutableCheck: true;
     serializableCheck: true;
-}>(options?: O): Array<Middleware<{}, S> | ThunkMiddlewareFor<S, O>>;
+}>(options?: O): MiddlewareArray<Middleware<{}, S> | ThunkMiddlewareFor<S, O>>;
 
 // @public
 export function getType<T extends string>(actionCreator: PayloadActionCreator<any, T>): T;
@@ -305,6 +297,18 @@ export function isImmutableDefault(value: unknown): boolean;
 // @public
 export function isPlain(val: any): boolean;
 
+// @public (undocumented)
+export class MiddlewareArray<Middlewares extends Middleware<any, any>> extends Array<Middlewares> {
+    // (undocumented)
+    concat<AdditionalMiddlewares extends ReadonlyArray<Middleware<any, any>>>(items: AdditionalMiddlewares): MiddlewareArray<Middlewares | AdditionalMiddlewares[number]>;
+    // (undocumented)
+    concat<AdditionalMiddlewares extends ReadonlyArray<Middleware<any, any>>>(...items: AdditionalMiddlewares): MiddlewareArray<Middlewares | AdditionalMiddlewares[number]>;
+    // (undocumented)
+    prepend<AdditionalMiddlewares extends ReadonlyArray<Middleware<any, any>>>(items: AdditionalMiddlewares): MiddlewareArray<AdditionalMiddlewares[number] | Middlewares>;
+    // (undocumented)
+    prepend<AdditionalMiddlewares extends ReadonlyArray<Middleware<any, any>>>(...items: AdditionalMiddlewares): MiddlewareArray<AdditionalMiddlewares[number] | Middlewares>;
+}
+
 // @public (undocumented)
 export let nanoid: (size?: number) => string;
 
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 00000000..5a67ec3c
--- /dev/null
+++ b/jest.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+  setupFilesAfterEnv: ['./jest.setup.js']
+}
diff --git a/jest.setup.js b/jest.setup.js
new file mode 100644
index 00000000..ed3889f8
--- /dev/null
+++ b/jest.setup.js
@@ -0,0 +1,4 @@
+process.on('unhandledRejection', error => {
+  // eslint-disable-next-line no-undef
+  fail(error)
+})
diff --git a/src/configureStore.ts b/src/configureStore.ts
index 907641be..14637f67 100644
--- a/src/configureStore.ts
+++ b/src/configureStore.ts
@@ -20,8 +20,9 @@ import {
 
 import isPlainObject from './isPlainObject'
 import {
-  getDefaultMiddleware,
-  ThunkMiddlewareFor
+  ThunkMiddlewareFor,
+  curryGetDefaultMiddleware,
+  CurriedGetDefaultMiddleware
 } from './getDefaultMiddleware'
 import { DispatchForMiddlewares } from './tsHelpers'
 
@@ -56,7 +57,7 @@ export interface ConfigureStoreOptions<
    * An array of Redux middleware to install. If not supplied, defaults to
    * the set of middleware returned by `getDefaultMiddleware()`.
    */
-  middleware?: M
+  middleware?: ((getDefaultMiddleware: CurriedGetDefaultMiddleware<S>) => M) | M
 
   /**
    * Whether to enable Redux DevTools integration. Defaults to `true`.
@@ -124,9 +125,11 @@ export function configureStore<
   A extends Action = AnyAction,
   M extends Middlewares<S> = [ThunkMiddlewareFor<S>]
 >(options: ConfigureStoreOptions<S, A, M>): EnhancedStore<S, A, M> {
+  const curriedGetDefaultMiddleware = curryGetDefaultMiddleware<S>()
+
   const {
     reducer = undefined,
-    middleware = getDefaultMiddleware(),
+    middleware = curriedGetDefaultMiddleware(),
     devTools = true,
     preloadedState = undefined,
     enhancers = undefined
@@ -144,7 +147,11 @@ export function configureStore<
     )
   }
 
-  const middlewareEnhancer = applyMiddleware(...middleware)
+  const middlewareEnhancer = applyMiddleware(
+    ...(typeof middleware === 'function'
+      ? middleware(curriedGetDefaultMiddleware)
+      : middleware)
+  )
 
   let finalCompose = compose
 
diff --git a/src/createAsyncThunk.ts b/src/createAsyncThunk.ts
index 8e6b3e2d..cab1d2c8 100644
--- a/src/createAsyncThunk.ts
+++ b/src/createAsyncThunk.ts
@@ -199,12 +199,12 @@ interface AsyncThunkOptions<
    * A method to control whether the asyncThunk should be executed. Has access to the
    * `arg`, `api.getState()` and `api.extra` arguments.
    *
-   * @returns `true` if the asyncThunk should be executed, `false` if it should be skipped
+   * @returns `false` if it should be skipped
    */
   condition?(
     arg: ThunkArg,
     api: Pick<GetThunkAPI<ThunkApiConfig>, 'getState' | 'extra'>
-  ): boolean
+  ): boolean | undefined
   /**
    * If `condition` returns `false`, the asyncThunk will be skipped.
    * This option allows you to control whether a `rejected` action with `meta.condition == false`
@@ -215,6 +215,71 @@ interface AsyncThunkOptions<
   dispatchConditionRejection?: boolean
 }
 
+type AsyncThunkPendingActionCreator<
+  ThunkArg
+> = ActionCreatorWithPreparedPayload<
+  [string, ThunkArg],
+  undefined,
+  string,
+  never,
+  {
+    arg: ThunkArg
+    requestId: string
+  }
+>
+
+type AsyncThunkRejectedActionCreator<
+  ThunkArg,
+  ThunkApiConfig
+> = ActionCreatorWithPreparedPayload<
+  [
+    Error | null,
+    string,
+    ThunkArg,
+    (GetRejectValue<ThunkApiConfig> | undefined)?
+  ],
+  GetRejectValue<ThunkApiConfig> | undefined,
+  string,
+  SerializedError,
+  {
+    arg: ThunkArg
+    requestId: string
+    aborted: boolean
+    condition: boolean
+  }
+>
+
+type AsyncThunkFulfilledActionCreator<
+  Returned,
+  ThunkArg
+> = ActionCreatorWithPreparedPayload<
+  [Returned, string, ThunkArg],
+  Returned,
+  string,
+  never,
+  {
+    arg: ThunkArg
+    requestId: string
+  }
+>
+
+/**
+ * A type describing the return value of `createAsyncThunk`.
+ * Might be useful for wrapping `createAsyncThunk` in custom abstractions.
+ *
+ * @public
+ */
+export type AsyncThunk<
+  Returned,
+  ThunkArg,
+  ThunkApiConfig extends AsyncThunkConfig
+> = AsyncThunkActionCreator<Returned, ThunkArg, ThunkApiConfig> & {
+  pending: AsyncThunkPendingActionCreator<ThunkArg>
+  rejected: AsyncThunkRejectedActionCreator<ThunkArg, ThunkApiConfig>
+  fulfilled: AsyncThunkFulfilledActionCreator<Returned, ThunkArg>
+  typePrefix: string
+}
+
 /**
  *
  * @param typePrefix
@@ -229,15 +294,9 @@ export function createAsyncThunk<
   ThunkApiConfig extends AsyncThunkConfig = {}
 >(
   typePrefix: string,
-  payloadCreator: (
-    arg: ThunkArg,
-    thunkAPI: GetThunkAPI<ThunkApiConfig>
-  ) =>
-    | Promise<Returned | RejectWithValue<GetRejectValue<ThunkApiConfig>>>
-    | Returned
-    | RejectWithValue<GetRejectValue<ThunkApiConfig>>,
+  payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, ThunkApiConfig>,
   options?: AsyncThunkOptions<ThunkArg, ThunkApiConfig>
-) {
+): AsyncThunk<Returned, ThunkArg, ThunkApiConfig> {
   type RejectedValue = GetRejectValue<ThunkApiConfig>
 
   const fulfilled = createAction(
@@ -326,9 +385,12 @@ If you want to use the AbortController to react to \`abort\` events, please cons
         )
       )
 
+      let started = false
       function abort(reason?: string) {
-        abortReason = reason
-        abortController.abort()
+        if (started) {
+          abortReason = reason
+          abortController.abort()
+        }
       }
 
       const promise = (async function() {
@@ -339,11 +401,13 @@ If you want to use the AbortController to react to \`abort\` events, please cons
             options.condition &&
             options.condition(arg, { getState, extra }) === false
           ) {
+            // eslint-disable-next-line no-throw-literal
             throw {
               name: 'ConditionError',
               message: 'Aborted due to condition callback returning false.'
             }
           }
+          started = true
           dispatch(pending(requestId, arg))
           finalAction = await Promise.race([
             abortedPromise,
diff --git a/src/getDefaultMiddleware.ts b/src/getDefaultMiddleware.ts
index 5efcf717..e5f57085 100644
--- a/src/getDefaultMiddleware.ts
+++ b/src/getDefaultMiddleware.ts
@@ -11,6 +11,7 @@ import {
   createSerializableStateInvariantMiddleware,
   SerializableStateInvariantMiddlewareOptions
 } from './serializableStateInvariantMiddleware'
+import { MiddlewareArray } from './utils'
 
 function isBoolean(x: any): x is boolean {
   return typeof x === 'boolean'
@@ -39,6 +40,24 @@ export type ThunkMiddlewareFor<
       | ThunkMiddleware<S, AnyAction, null> //The ThunkMiddleware with a `null` ExtraArgument is here to provide backwards-compatibility.
       | ThunkMiddleware<S, AnyAction>
 
+export type CurriedGetDefaultMiddleware<S = any> = <
+  O extends Partial<GetDefaultMiddlewareOptions> = {
+    thunk: true
+    immutableCheck: true
+    serializableCheck: true
+  }
+>(
+  options?: O
+) => MiddlewareArray<Middleware<{}, S> | ThunkMiddlewareFor<S, O>>
+
+export function curryGetDefaultMiddleware<
+  S = any
+>(): CurriedGetDefaultMiddleware<S> {
+  return function curriedGetDefaultMiddleware(options) {
+    return getDefaultMiddleware(options)
+  }
+}
+
 /**
  * Returns any array containing the default middleware installed by
  * `configureStore()`. Useful if you want to configure your store with a custom
@@ -55,14 +74,16 @@ export function getDefaultMiddleware<
     immutableCheck: true
     serializableCheck: true
   }
->(options: O = {} as O): Array<Middleware<{}, S> | ThunkMiddlewareFor<S, O>> {
+>(
+  options: O = {} as O
+): MiddlewareArray<Middleware<{}, S> | ThunkMiddlewareFor<S, O>> {
   const {
     thunk = true,
     immutableCheck = true,
     serializableCheck = true
   } = options
 
-  let middlewareArray: Middleware<{}, S>[] = []
+  let middlewareArray: Middleware<{}, S>[] = new MiddlewareArray()
 
   if (thunk) {
     if (isBoolean(thunk)) {
diff --git a/src/index.ts b/src/index.ts
index f4a1843d..aa296e66 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -81,6 +81,7 @@ export {
   // types
   ActionReducerMapBuilder
 } from './mapBuilders'
+export { MiddlewareArray } from './utils'
 
 export { createEntityAdapter } from './entities/create_adapter'
 export {
@@ -96,6 +97,7 @@ export {
 } from './entities/models'
 
 export {
+  AsyncThunk,
   AsyncThunkAction,
   AsyncThunkPayloadCreatorReturnValue,
   AsyncThunkPayloadCreator,
diff --git a/src/utils.ts b/src/utils.ts
index 933c7f4a..5d9d4205 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,3 +1,5 @@
+import { Middleware } from 'redux'
+
 export function getTimeMeasureUtils(maxDelay: number, fnName: string) {
   let elapsed = 0
   return {
@@ -19,3 +21,37 @@ It is disabled in production builds, so you don't need to worry about that.`)
     }
   }
 }
+
+/**
+ * @public
+ */
+export class MiddlewareArray<
+  Middlewares extends Middleware<any, any>
+> extends Array<Middlewares> {
+  concat<AdditionalMiddlewares extends ReadonlyArray<Middleware<any, any>>>(
+    items: AdditionalMiddlewares
+  ): MiddlewareArray<Middlewares | AdditionalMiddlewares[number]>
+
+  concat<AdditionalMiddlewares extends ReadonlyArray<Middleware<any, any>>>(
+    ...items: AdditionalMiddlewares
+  ): MiddlewareArray<Middlewares | AdditionalMiddlewares[number]>
+
+  concat(...arr: any[]) {
+    return new MiddlewareArray(...super.concat(...arr))
+  }
+
+  prepend<AdditionalMiddlewares extends ReadonlyArray<Middleware<any, any>>>(
+    items: AdditionalMiddlewares
+  ): MiddlewareArray<AdditionalMiddlewares[number] | Middlewares>
+
+  prepend<AdditionalMiddlewares extends ReadonlyArray<Middleware<any, any>>>(
+    ...items: AdditionalMiddlewares
+  ): MiddlewareArray<AdditionalMiddlewares[number] | Middlewares>
+
+  prepend(...arr: any[]) {
+    if (arr.length === 1 && Array.isArray(arr[0])) {
+      return new MiddlewareArray(...arr[0].concat(this))
+    }
+    return new MiddlewareArray(...arr.concat(this))
+  }
+}
diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js
index c0ee0de1..570dbc67 100644
--- a/website/docusaurus.config.js
+++ b/website/docusaurus.config.js
@@ -27,7 +27,6 @@ module.exports = {
     repoUrl: 'https://github.com/reduxjs/redux-toolkit'
   },
   themeConfig: {
-    disableDarkMode: true,
     prism: {
       theme: require('./src/js/monokaiTheme.js')
     },
diff --git a/website/src/css/custom.css b/website/src/css/custom.css
index b0cce427..82258b8f 100644
--- a/website/src/css/custom.css
+++ b/website/src/css/custom.css
@@ -20,7 +20,7 @@
 
   --ifm-blockquote-color: #ecf4f9;
   --ifm-blockquote-color-dark: #cbddea;
-  
+
   --ifm-code-padding-vertical: 0.1rem;
   --ifm-code-padding-horizontal: 0.2rem;
 
@@ -74,11 +74,23 @@ code {
   border-radius: 0.2rem;
 }
 
+html[data-theme='dark'] code {
+  background-color: var(--ifm-color-emphasis-200);
+  border-radius: 0.2rem;
+}
+
 a code,
 code a {
+  background-color: var(--ifm-color-emphasis-200);
   color: inherit;
 }
 
+html[data-theme='dark'] a code,
+html[data-theme='dark'] code a {
+  background-color: var(--ifm-color-emphasis-100);
+  color: var(--ifm-color-primary-lighter);
+}
+
 a.contents__link > code {
   color: inherit;
 }
diff --git a/website/src/pages/index.js b/website/src/pages/index.js
index 00626078..6fff0b6b 100644
--- a/website/src/pages/index.js
+++ b/website/src/pages/index.js
@@ -110,7 +110,7 @@ const otherLibraries = [
     image: (
       <svg
         xmlns="http://www.w3.org/2000/svg"
-        ariaHidden="true"
+        aria-hidden="true"
         data-icon="external-link-square-alt"
         data-prefix="fas"
         viewBox="0 0 448 512"


Test patch:
diff --git a/src/configureStore.test.ts b/src/configureStore.test.ts
index a7a4bab6..b8f1f726 100644
--- a/src/configureStore.test.ts
+++ b/src/configureStore.test.ts
@@ -84,6 +84,26 @@ describe('configureStore', () => {
     })
   })
 
+  describe('middleware builder notation', () => {
+    it('calls builder, passes getDefaultMiddleware and uses returned middlewares', () => {
+      const thank = jest.fn((_store => next => action =>
+        'foobar') as redux.Middleware)
+
+      const builder = jest.fn(getDefaultMiddleware => {
+        expect(getDefaultMiddleware).toEqual(expect.any(Function))
+        expect(getDefaultMiddleware()).toEqual(expect.any(Array))
+
+        return [thank]
+      })
+
+      const store = configureStore({ middleware: builder, reducer })
+
+      expect(builder).toHaveBeenCalled()
+
+      expect(store.dispatch({ type: 'test' })).toBe('foobar')
+    })
+  })
+
   describe('with devTools disabled', () => {
     it('calls createStore without devTools enhancer', () => {
       expect(configureStore({ devTools: false, reducer })).toBeInstanceOf(
diff --git a/src/createAsyncThunk.test.ts b/src/createAsyncThunk.test.ts
index 49969318..8e52f864 100644
--- a/src/createAsyncThunk.test.ts
+++ b/src/createAsyncThunk.test.ts
@@ -549,6 +549,21 @@ describe('conditional skipping of asyncThunks', () => {
     expect(dispatch).toHaveBeenCalledTimes(0)
   })
 
+  test('does not fail when attempting to abort a canceled promise', async () => {
+    const asyncPayloadCreator = jest.fn(async (x: typeof arg) => {
+      await new Promise(resolve => setTimeout(resolve, 2000))
+      return 10
+    })
+
+    const asyncThunk = createAsyncThunk('test', asyncPayloadCreator, {
+      condition
+    })
+    const promise = asyncThunk(arg)(dispatch, getState, extra)
+    promise.abort(
+      `If the promise was 1. somehow canceled, 2. in a 'started' state and 3. we attempted to abort, this would crash the tests`
+    )
+  })
+
   test('rejected action can be dispatched via option', async () => {
     const asyncThunk = createAsyncThunk('test', payloadCreator, {
       condition,
diff --git a/src/getDefaultMiddleware.test.ts b/src/getDefaultMiddleware.test.ts
index 6791d191..82ccfc31 100644
--- a/src/getDefaultMiddleware.test.ts
+++ b/src/getDefaultMiddleware.test.ts
@@ -1,6 +1,5 @@
-import { AnyAction } from 'redux'
-import { getDefaultMiddleware } from './getDefaultMiddleware'
-import { configureStore } from './configureStore'
+import { AnyAction, Middleware } from 'redux'
+import { getDefaultMiddleware, MiddlewareArray, configureStore } from '.'
 import thunk, { ThunkAction } from 'redux-thunk'
 
 describe('getDefaultMiddleware', () => {
@@ -116,3 +115,120 @@ describe('getDefaultMiddleware', () => {
     expect(serializableCheckWasCalled).toBe(true)
   })
 })
+
+describe('MiddlewareArray functionality', () => {
+  const middleware1: Middleware = () => next => action => next(action)
+  const middleware2: Middleware = () => next => action => next(action)
+  const defaultMiddleware = getDefaultMiddleware()
+  const originalDefaultMiddleware = [...defaultMiddleware]
+
+  test('allows to prepend a single value', () => {
+    const prepended = defaultMiddleware.prepend(middleware1)
+
+    // value is prepended
+    expect(prepended).toEqual([middleware1, ...defaultMiddleware])
+    // returned value is of correct type
+    expect(prepended).toBeInstanceOf(MiddlewareArray)
+    // prepended is a new array
+    expect(prepended).not.toEqual(defaultMiddleware)
+    // defaultMiddleware is not modified
+    expect(defaultMiddleware).toEqual(originalDefaultMiddleware)
+  })
+
+  test('allows to prepend multiple values (array as first argument)', () => {
+    const prepended = defaultMiddleware.prepend([middleware1, middleware2])
+
+    // value is prepended
+    expect(prepended).toEqual([middleware1, middleware2, ...defaultMiddleware])
+    // returned value is of correct type
+    expect(prepended).toBeInstanceOf(MiddlewareArray)
+    // prepended is a new array
+    expect(prepended).not.toEqual(defaultMiddleware)
+    // defaultMiddleware is not modified
+    expect(defaultMiddleware).toEqual(originalDefaultMiddleware)
+  })
+
+  test('allows to prepend multiple values (rest)', () => {
+    const prepended = defaultMiddleware.prepend(middleware1, middleware2)
+
+    // value is prepended
+    expect(prepended).toEqual([middleware1, middleware2, ...defaultMiddleware])
+    // returned value is of correct type
+    expect(prepended).toBeInstanceOf(MiddlewareArray)
+    // prepended is a new array
+    expect(prepended).not.toEqual(defaultMiddleware)
+    // defaultMiddleware is not modified
+    expect(defaultMiddleware).toEqual(originalDefaultMiddleware)
+  })
+
+  test('allows to concat a single value', () => {
+    const concatenated = defaultMiddleware.concat(middleware1)
+
+    // value is concatenated
+    expect(concatenated).toEqual([...defaultMiddleware, middleware1])
+    // returned value is of correct type
+    expect(concatenated).toBeInstanceOf(MiddlewareArray)
+    // concatenated is a new array
+    expect(concatenated).not.toEqual(defaultMiddleware)
+    // defaultMiddleware is not modified
+    expect(defaultMiddleware).toEqual(originalDefaultMiddleware)
+  })
+
+  test('allows to concat multiple values (array as first argument)', () => {
+    const concatenated = defaultMiddleware.concat([middleware1, middleware2])
+
+    // value is concatenated
+    expect(concatenated).toEqual([
+      ...defaultMiddleware,
+      middleware1,
+      middleware2
+    ])
+    // returned value is of correct type
+    expect(concatenated).toBeInstanceOf(MiddlewareArray)
+    // concatenated is a new array
+    expect(concatenated).not.toEqual(defaultMiddleware)
+    // defaultMiddleware is not modified
+    expect(defaultMiddleware).toEqual(originalDefaultMiddleware)
+  })
+
+  test('allows to concat multiple values (rest)', () => {
+    const concatenated = defaultMiddleware.concat(middleware1, middleware2)
+
+    // value is concatenated
+    expect(concatenated).toEqual([
+      ...defaultMiddleware,
+      middleware1,
+      middleware2
+    ])
+    // returned value is of correct type
+    expect(concatenated).toBeInstanceOf(MiddlewareArray)
+    // concatenated is a new array
+    expect(concatenated).not.toEqual(defaultMiddleware)
+    // defaultMiddleware is not modified
+    expect(defaultMiddleware).toEqual(originalDefaultMiddleware)
+  })
+
+  test('allows to concat and then prepend', () => {
+    const concatenated = defaultMiddleware
+      .concat(middleware1)
+      .prepend(middleware2)
+
+    expect(concatenated).toEqual([
+      middleware2,
+      ...defaultMiddleware,
+      middleware1
+    ])
+  })
+
+  test('allows to prepend and then concat', () => {
+    const concatenated = defaultMiddleware
+      .prepend(middleware2)
+      .concat(middleware1)
+
+    expect(concatenated).toEqual([
+      middleware2,
+      ...defaultMiddleware,
+      middleware1
+    ])
+  })
+})
diff --git a/type-tests/files/MiddlewareArray.typetest.ts b/type-tests/files/MiddlewareArray.typetest.ts
new file mode 100644
index 00000000..eb77189e
--- /dev/null
+++ b/type-tests/files/MiddlewareArray.typetest.ts
@@ -0,0 +1,114 @@
+import { getDefaultMiddleware } from 'src/getDefaultMiddleware'
+import { Middleware } from 'redux'
+import { DispatchForMiddlewares } from 'src/tsHelpers'
+
+declare const expectType: <T>(t: T) => T
+
+declare const middleware1: Middleware<{
+  (_: string): number
+}>
+
+declare const middleware2: Middleware<{
+  (_: number): string
+}>
+
+declare const getDispatch: <M extends Array<Middleware>>(
+  m: M
+) => DispatchForMiddlewares<M>
+
+type ThunkReturn = Promise<'thunk'>
+declare const thunkCreator: () => () => ThunkReturn
+
+{
+  const defaultMiddleware = getDefaultMiddleware()
+
+  // prepend single element
+  {
+    const concatenated = defaultMiddleware.prepend(middleware1)
+    const dispatch = getDispatch(concatenated)
+    expectType<number>(dispatch('foo'))
+    expectType<ThunkReturn>(dispatch(thunkCreator()))
+
+    // typings:expect-error
+    expectType<string>(dispatch('foo'))
+  }
+
+  // prepepend multiple (rest)
+  {
+    const concatenated = defaultMiddleware.prepend(middleware1, middleware2)
+    const dispatch = getDispatch(concatenated)
+    expectType<number>(dispatch('foo'))
+    expectType<string>(dispatch(5))
+    expectType<ThunkReturn>(dispatch(thunkCreator()))
+
+    // typings:expect-error
+    expectType<string>(dispatch('foo'))
+  }
+
+  // prepend multiple (array notation)
+  {
+    const concatenated = defaultMiddleware.prepend([
+      middleware1,
+      middleware2
+    ] as const)
+    const dispatch = getDispatch(concatenated)
+    expectType<number>(dispatch('foo'))
+    expectType<string>(dispatch(5))
+    expectType<ThunkReturn>(dispatch(thunkCreator()))
+
+    // typings:expect-error
+    expectType<string>(dispatch('foo'))
+  }
+
+  // concat single element
+  {
+    const concatenated = defaultMiddleware.concat(middleware1)
+    const dispatch = getDispatch(concatenated)
+    expectType<number>(dispatch('foo'))
+    expectType<ThunkReturn>(dispatch(thunkCreator()))
+
+    // typings:expect-error
+    expectType<string>(dispatch('foo'))
+  }
+
+  // prepepend multiple (rest)
+  {
+    const concatenated = defaultMiddleware.concat(middleware1, middleware2)
+    const dispatch = getDispatch(concatenated)
+    expectType<number>(dispatch('foo'))
+    expectType<string>(dispatch(5))
+    expectType<ThunkReturn>(dispatch(thunkCreator()))
+
+    // typings:expect-error
+    expectType<string>(dispatch('foo'))
+  }
+
+  // concat multiple (array notation)
+  {
+    const concatenated = defaultMiddleware.concat([
+      middleware1,
+      middleware2
+    ] as const)
+    const dispatch = getDispatch(concatenated)
+    expectType<number>(dispatch('foo'))
+    expectType<string>(dispatch(5))
+    expectType<ThunkReturn>(dispatch(thunkCreator()))
+
+    // typings:expect-error
+    expectType<string>(dispatch('foo'))
+  }
+
+  // concat and prepend
+  {
+    const concatenated = defaultMiddleware
+      .concat(middleware1)
+      .prepend(middleware2)
+    const dispatch = getDispatch(concatenated)
+    expectType<number>(dispatch('foo'))
+    expectType<string>(dispatch(5))
+    expectType<ThunkReturn>(dispatch(thunkCreator()))
+
+    // typings:expect-error
+    expectType<string>(dispatch('foo'))
+  }
+}
diff --git a/type-tests/files/configureStore.typetest.ts b/type-tests/files/configureStore.typetest.ts
index 957b2432..5b43eed7 100644
--- a/type-tests/files/configureStore.typetest.ts
+++ b/type-tests/files/configureStore.typetest.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-lone-blocks */
 import {
   applyMiddleware,
   Dispatch,
@@ -9,6 +10,11 @@ import {
 import { configureStore, PayloadAction, getDefaultMiddleware } from 'src'
 import thunk, { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk'
 
+const _anyMiddleware: any = () => () => () => {}
+function expectType<T>(t: T) {
+  return t
+}
+
 /*
  * Test: configureStore() requires a valid reducer or reducer map.
  */
@@ -270,4 +276,118 @@ import thunk, { ThunkMiddleware, ThunkAction, ThunkDispatch } from 'redux-thunk'
     // typings:expect-error
     store.dispatch(function() {} as ThunkAction<void, {}, boolean, AnyAction>)
   }
+
+  /**
+   * Test: custom middleware and getDefaultMiddleware
+   */
+  {
+    const store = configureStore({
+      reducer: reducerA,
+      middleware: [
+        ((() => {}) as any) as Middleware<(a: 'a') => 'A', StateA>,
+        ...getDefaultMiddleware<StateA>()
+      ] as const
+    })
+    const result1: 'A' = store.dispatch('a')
+    const result2: Promise<'A'> = store.dispatch(thunkA())
+    // typings:expect-error
+    store.dispatch(thunkB())
+  }
+
+  /**
+   * Test: custom middleware and getDefaultMiddleware, using prepend
+   */
+  {
+    const otherMiddleware: Middleware<(a: 'a') => 'A', StateA> = _anyMiddleware
+    const concatenated = getDefaultMiddleware<StateA>().prepend(otherMiddleware)
+
+    expectType<
+      ReadonlyArray<typeof otherMiddleware | ThunkMiddleware | Middleware<{}>>
+    >(concatenated)
+
+    const store = configureStore({
+      reducer: reducerA,
+      middleware: concatenated
+    })
+    const result1: 'A' = store.dispatch('a')
+    const result2: Promise<'A'> = store.dispatch(thunkA())
+    // typings:expect-error
+    store.dispatch(thunkB())
+  }
+
+  /**
+   * Test: custom middleware and getDefaultMiddleware, using concat
+   */
+  {
+    const otherMiddleware: Middleware<(a: 'a') => 'A', StateA> = _anyMiddleware
+    const concatenated = getDefaultMiddleware<StateA>().concat(otherMiddleware)
+
+    expectType<
+      ReadonlyArray<typeof otherMiddleware | ThunkMiddleware | Middleware<{}>>
+    >(concatenated)
+
+    const store = configureStore({
+      reducer: reducerA,
+      middleware: concatenated
+    })
+    const result1: 'A' = store.dispatch('a')
+    const result2: Promise<'A'> = store.dispatch(thunkA())
+    // typings:expect-error
+    store.dispatch(thunkB())
+  }
+
+  /**
+   * Test: middlewareBuilder notation, getDefaultMiddleware (unconfigured)
+   */
+  {
+    const store = configureStore({
+      reducer: reducerA,
+      middleware: getDefaultMiddleware =>
+        [
+          ((() => {}) as any) as Middleware<(a: 'a') => 'A', StateA>,
+          ...getDefaultMiddleware()
+        ] as const
+    })
+    const result1: 'A' = store.dispatch('a')
+    const result2: Promise<'A'> = store.dispatch(thunkA())
+    // typings:expect-error
+    store.dispatch(thunkB())
+  }
+
+  /**
+   * Test: middlewareBuilder notation, getDefaultMiddleware, concat & prepend
+   */
+  {
+    const otherMiddleware: Middleware<(a: 'a') => 'A', StateA> = _anyMiddleware
+    const otherMiddleware2: Middleware<(a: 'b') => 'B', StateA> = _anyMiddleware
+    const store = configureStore({
+      reducer: reducerA,
+      middleware: getDefaultMiddleware =>
+        getDefaultMiddleware()
+          .concat(otherMiddleware)
+          .prepend(otherMiddleware2)
+    })
+    const result1: 'A' = store.dispatch('a')
+    const result2: Promise<'A'> = store.dispatch(thunkA())
+    const result3: 'B' = store.dispatch('b')
+    // typings:expect-error
+    store.dispatch(thunkB())
+  }
+
+  /**
+   * Test: middlewareBuilder notation, getDefaultMiddleware (thunk: false)
+   */
+  {
+    const store = configureStore({
+      reducer: reducerA,
+      middleware: getDefaultMiddleware =>
+        [
+          ((() => {}) as any) as Middleware<(a: 'a') => 'A', StateA>,
+          ...getDefaultMiddleware({ thunk: false })
+        ] as const
+    })
+    const result1: 'A' = store.dispatch('a')
+    // typings:expect-error
+    store.dispatch(thunkA())
+  }
 }
diff --git a/type-tests/files/min-3.4/configureStore.typetest.ts b/type-tests/files/min-3.4/configureStore.typetest.ts
deleted file mode 100644
index 4a739947..00000000
--- a/type-tests/files/min-3.4/configureStore.typetest.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Dispatch, Middleware } from 'redux'
-import { configureStore, getDefaultMiddleware } from 'src'
-import { ThunkAction } from 'redux-thunk'
-
-/**
- * Test: Dispatch typings
- */
-{
-  type StateA = number
-  const reducerA = () => 0
-  function thunkA() {
-    return ((() => {}) as any) as ThunkAction<Promise<'A'>, StateA, any, any>
-  }
-
-  type StateB = string
-  function thunkB() {
-    return (dispatch: Dispatch, getState: () => StateB) => {}
-  }
-
-  /**
-   * Test: custom middleware and getDefaultMiddleware
-   */
-  {
-    const store = configureStore({
-      reducer: reducerA,
-      middleware: [
-        ((() => {}) as any) as Middleware<(a: 'a') => 'A', StateA>,
-        ...getDefaultMiddleware<StateA>()
-      ] as const
-    })
-    const result1: 'A' = store.dispatch('a')
-    const result2: Promise<'A'> = store.dispatch(thunkA())
-    // typings:expect-error
-    store.dispatch(thunkB())
-  }
-}
diff --git a/type-tests/lib.ts b/type-tests/lib.ts
index d8c37341..0128e2bd 100644
--- a/type-tests/lib.ts
+++ b/type-tests/lib.ts
@@ -15,3 +15,13 @@ export function checkDirectory(path: string, bail: boolean = false, depth = 1) {
 
   check(files, tsConfigPath, bail)
 }
+
+export function checkFile(path: string, bail: boolean = false, depth = 1) {
+  const tsConfigPath = findConfigFile(path, sys.fileExists)
+
+  if (!tsConfigPath) {
+    throw new Error(`Cannot find TypeScript config file in ${path}.`)
+  }
+
+  check([path], tsConfigPath, bail)
+}
diff --git a/type-tests/types-min-3.4.test.ts b/type-tests/types-min-3.4.test.ts
deleted file mode 100644
index b4c04c6b..00000000
--- a/type-tests/types-min-3.4.test.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { testIf, checkDirectory, tsVersion } from './lib'
-
-testIf(tsVersion >= 3.4)('Types >= 3.4', () => {
-  checkDirectory(`${__dirname}/files/min-3.4`)
-})