javascript - Typescript: Delvis typisering av en react-navigation spionhjelper


(Dette er noe relatert til dette tre år gamle innlegget. Jeg har prøvd å følge svarene, men de løste ikke problemet mitt.)

Jeg vil lage en generell mock (spion) navigator som jeg kan sende inn slik jeg gjør her med spyNavigation:

  it('shows an error if the request fails', async () => {
    const spyNavigation = {
      replace: jest.fn(),
      push: jest.fn(),
    }
    mockServer.use(
      rest.get('*/users/current-user', (req, res, ctx) => {
        return res(ctx.status(200), ctx.json({ id: 1 }))
      })
    )
    mockServer.use(
      rest.get('*/some-data', (req, res, ctx) => {
        return res(ctx.status(500))
      })
    )

    // @ts-ignore
    TestHelper.renderWithReactQueryClient(<OverviewScreen navigation={spyNavigation} />)

    await TestHelper.waitForWithExtendedTimeOut(() => {
      expect(spyNavigation.push).toHaveBeenCalledWith('Error', {
        error: new Error('Request failed with status code 500'),
      })
    })
  })

Jeg vil gjerne abstrahere spyNavigation til en testhjelper og få TypeScript til å hjelpe meg med delvis typisering av den. Slik vil det fullføre metoder som 'replace', 'push' osv.

TypeScript-en til react-navigation er bare så kompleks for meg å forstå. Det hadde vært greit om spionen ikke vet om navnene Screens/Navigators - bare å være et generelt "navigasjons"-objekt.

import { BottomTabScreenProps } from '@react-navigation/bottom-tabs'
import { CompositeScreenProps, NavigatorScreenParams } from '@react-navigation/native'
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import { AxiosError } from 'axios'

declare global {
  namespace ReactNavigation {
    interface RootParamList extends RootStackParamList {}
  }
}

export type RootStackParamList = {
  SignInOrCreateUser: undefined
  SignIn: undefined
  Root: NavigatorScreenParams<RootTabParamList> | undefined
  Modal: undefined
  Error: { error: AxiosError | null } | undefined
}

export type RootTabParamList = {
  Overview: undefined
  Returns: undefined
  Portfolio: undefined
  More: undefined
}

export type RouteName = keyof RootStackParamList | keyof RootTabParamList

export const routeTypeGuard = (route: RouteName) => route

export type RootStackScreenProps<Screen extends keyof RootStackParamList> = NativeStackScreenProps<
  RootStackParamList,
  Screen
>

export type RootTabScreenProps<Screen extends keyof RootTabParamList> = CompositeScreenProps<
  BottomTabScreenProps<RootTabParamList, Screen>,
  NativeStackScreenProps<RootStackParamList>
>

dette fungerer ikke

import { NativeStackNavigatorProps } from '@react-navigation/native-stack/lib/typescript/src/types'

export const mockNavigation: NativeStackNavigatorProps = {
  navigate: jest.fn(),
  push: jest.fn(),
  replace: jest.fn(),
  pop: jest.fn(),
}
Type '{ navigate: jest.Mock<any, any>; push: jest.Mock<any, any>; replace: jest.Mock<any, any>; pop: jest.Mock<any, any>; }' is not assignable to type 'NativeStackNavigatorProps'.
  Object literal may only specify known properties, and 'navigate' does not exist in type 'NativeStackNavigatorProps'.ts(2322)

Jeg har et gjetning på at det burde være noe som

export const mockNavigation: Record<keyof ???, () => ReturnType<typeof jest.fn>> = {
  navigate: jest.fn(),
  push: jest.fn(),
  replace: jest.fn(),
  pop: jest.fn(),
}

Svar #1

Her er en potensiell løsning på spørsmålet ditt etter kommentarene dine! Jeg håper dette fungerer for deg. Jeg utvider StackNavigationProp som har navnene på funksjonene du ønsker: navigate, push osv. Også ut fra spørsmålet ditt, erstatter jeg typene på egenskapene dens med returverdien til jest.fn.

De valgfrie generiske typene for MockStackNavigationProp bør også speile de for StackNavigationProp, og lar deg spesifisere om ParamList er tilgjengelig.

Hvis dette fungerer, lurer jeg på om det også kunne tas videre for å automatisk utlede argumenttypene til jest-funksjonsmockene fra de opprinnelige funksjonsdefinisjonene.

import { ParamListBase } from "@react-navigation/native";
import { StackNavigationProp } from "@react-navigation/stack";

type MockStackNavigationProp<
  ParamList extends ParamListBase = any,
  RouteName extends keyof ParamList = string
> = Record<
  keyof StackNavigationProp<ParamList, RouteName>,
  ReturnType<typeof jest.fn>
>;

const mockNavigation: MockStackNavigationProp = {
  navigate: jest.fn(),
  pop: jest.fn(),
  push: jest.fn()
  //.. and more, OR use Partial<MockStackNavigationProp>
};

CodeSandbox-lenke.