Skip to content

Commit

Permalink
refactor: add typings to most of the functions
Browse files Browse the repository at this point in the history
Some runtime logic had to be updated,
mostly adding more strict checks to avoid errors
  • Loading branch information
acezard committed Nov 11, 2023
1 parent 63a75e7 commit 50b5ed7
Show file tree
Hide file tree
Showing 19 changed files with 193 additions and 104 deletions.
17 changes: 11 additions & 6 deletions src/hooks/useSession.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import { useClient, useCapabilities } from 'cozy-client'

import { useEffect, useMemo, useState } from 'react'

import { makeSessionAPI } from '/libs/functions/session'
import { useClient, useCapabilities } from 'cozy-client'

import { makeSessionAPI, SessionApi } from '/libs/functions/session'

export const useSession = () => {
const [subdomainType, setSubdomainType] = useState()
export const useSession = (): SessionApi => {
const [subdomainType, setSubdomainType] = useState<string>()
const client = useClient()
const { capabilities, fetchStatus } = useCapabilities(client)

useEffect(() => {
fetchStatus === 'loaded' &&
// @ts-expect-error : cozy-client has to be updated
setSubdomainType(capabilities?.flat_subdomains ? 'flat' : 'nested')
}, [capabilities, fetchStatus])

return useMemo(
() => makeSessionAPI(client, subdomainType),
// We have to assume that client and subdomainType are defined
// Still, this is old code and we should probably refactor it
// Adding a @TODO flag for now
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
() => makeSessionAPI(client!, subdomainType!),
[client, subdomainType]
)
}
18 changes: 11 additions & 7 deletions src/libs/RootNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,25 @@ import Minilog from 'cozy-minilog'

const log = Minilog('RootNavigation')

export const navigationRef = createNavigationContainerRef()
export const navigationRef =
createNavigationContainerRef<Record<string, unknown>>()

export const getCurrentRouteName = () => {
export const getCurrentRouteName = (): string | null => {
if (!navigationRef.isReady()) {
return null
}

return navigationRef.getCurrentRoute().name
return navigationRef.getCurrentRoute()?.name ?? null
}

const isReady = () => navigationRef.isReady()
const isReady = (): boolean => navigationRef.isReady()

export const goBack = () => navigationRef.goBack()
export const goBack = (): void => navigationRef.goBack()

export const navigate = (name, params) => {
export const navigate = (
name: string,
params?: Record<string, unknown>
): void => {
try {
if (isReady()) return navigationRef.navigate(name, params)

Expand All @@ -34,7 +38,7 @@ export const navigate = (name, params) => {
}
}

export const reset = (name, params = {}) => {
export const reset = (name: string, params = {}): void => {
try {
if (isReady())
return navigationRef.dispatch(
Expand Down
4 changes: 4 additions & 0 deletions src/libs/functions/getHostname.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ it('does not throw if no url key', () => {
it('does not throw if no event at all', () => {
expect(getHostname()).toBeUndefined()
})

it('does not throw if event with empty url', () => {
expect(getHostname({ url: undefined })).toBeUndefined()
})
21 changes: 18 additions & 3 deletions src/libs/functions/getHostname.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
export const getHostname = nativeEvent => {
import { getErrorMessage } from 'cozy-intent'

import { devlog } from '/core/tools/env'

export const getHostname = (
nativeEvent?: { url?: string | unknown } | unknown
): string | undefined => {
if (
!nativeEvent ||
typeof nativeEvent !== 'object' ||
!('url' in nativeEvent) ||
typeof nativeEvent.url !== 'string'
)
return

try {
return new URL(nativeEvent.url).hostname
} catch {
return nativeEvent?.url
} catch (error) {
devlog('getHostname failed, nativeEvent:', nativeEvent, error)
if (getErrorMessage(error).includes('Invalid URL')) return nativeEvent.url
}
}
18 changes: 8 additions & 10 deletions src/libs/functions/isSecureProtocol.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type CozyClient from 'cozy-client'

import { isSecureProtocol } from './isSecureProtocol'

jest.mock('cozy-client', () => ({
Expand All @@ -6,25 +8,21 @@ jest.mock('cozy-client', () => ({

describe('isSecureProtocol', () => {
it(`shoud return true if cozy-client's URL uses HTTPs protocol`, () => {
const client = {
getStackClient: () => ({
const result = isSecureProtocol({
getStackClient: (): { uri: string } => ({
uri: 'https://localhost:8080'
})
}

const result = isSecureProtocol(client)
} as unknown as jest.Mocked<CozyClient>)

expect(result).toBe(true)
})

it(`shoud return false if cozy-client's URL uses HTTP protocol`, () => {
const client = {
getStackClient: () => ({
const result = isSecureProtocol({
getStackClient: (): { uri: string } => ({
uri: 'http://localhost:8080'
})
}

const result = isSecureProtocol(client)
} as unknown as jest.Mocked<CozyClient>)

expect(result).toBe(false)
})
Expand Down
4 changes: 3 additions & 1 deletion src/libs/functions/isSecureProtocol.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export const isSecureProtocol = client => {
import type CozyClient from 'cozy-client'

export const isSecureProtocol = (client: CozyClient): boolean => {
const instanceUrl = new URL(client.getStackClient().uri)

return instanceUrl.protocol === 'https:'
Expand Down
2 changes: 2 additions & 0 deletions src/libs/functions/makeHandlers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { makeHandlers } from '/libs/functions/makeHandlers'

it('does not throw with bad HOF bad closure', () => {
// @ts-expect-error : we want to test this case
const badHandler = makeHandlers(NaN)
expect(() => badHandler()).not.toThrow()
})
Expand All @@ -17,6 +18,7 @@ it('does not throw with good HOF bad closure 2', () => {

it('does not throw with good HOF bad closure 3', () => {
const goodHandler = makeHandlers({ foo: jest.fn() })
// @ts-expect-error : we want to test this case
expect(() => goodHandler({ nativeEvent: '19' })).not.toThrow()
})

Expand Down
17 changes: 13 additions & 4 deletions src/libs/functions/makeHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
export const makeHandlers = handlers => event =>
type makeHandlersType = (
handlers?: Record<string, () => void>
) => (event?: { nativeEvent?: { data?: string | unknown } }) => void

export const makeHandlers: makeHandlersType = handlers => event =>
Object.keys(handlers?.constructor === Object ? handlers : {}).forEach(
handlerName =>
event?.nativeEvent?.data?.includes?.(handlerName) &&
handlers[handlerName]?.()
handlerName => {
const data = event?.nativeEvent?.data
const isString = typeof data === 'string'

if (!isString) return

return data.includes(handlerName) && handlers?.[handlerName]?.()
}
)
36 changes: 17 additions & 19 deletions src/libs/functions/routeHelpers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,60 @@
import { consumeRouteParameter } from './routeHelpers'
import { NavigationProp, RouteProp } from '@react-navigation/native'

import { consumeRouteParameter } from '/libs/functions/routeHelpers'

const navigation = {
setParams: jest.fn()
} as unknown as NavigationProp<Record<string, object | undefined>, string> & {
setParams: jest.Mock
}

describe('consumeRouteParameter', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('should return parameter and clear it from navigation when parameter exist', async () => {
it('should return parameter and clear it from navigation when parameter exist', () => {
const route = {
params: {
foo: 'bar'
}
}

const navigation = {
setParams: jest.fn()
}
} as RouteProp<Record<string, object | undefined>, string>

const param = consumeRouteParameter('foo', route, navigation)

expect(param).toBe('bar')
expect(navigation.setParams).toHaveBeenCalled()
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(navigation.setParams.mock.calls[0][0]).toStrictEqual({
foo: undefined
})
})

it('should return undefined and should not try to clear it from navigation when parameter does not exist', async () => {
it('should return undefined and should not try to clear it from navigation when parameter does not exist', () => {
const route = {
params: {
foo: 'bar'
}
}

const navigation = {
setParams: jest.fn()
}
} as RouteProp<Record<string, object | undefined>, string>

const param = consumeRouteParameter('unexistingParam', route, navigation)

expect(param).toBe(undefined)
expect(navigation.setParams).not.toHaveBeenCalled()
})

it('should return parameter and clear it from navigation when parameter exist but has falsy value', async () => {
it('should return parameter and clear it from navigation when parameter exist but has falsy value', () => {
const route = {
params: {
foo: 0
}
}

const navigation = {
setParams: jest.fn()
}
} as RouteProp<Record<string, object | undefined>, string>

const param = consumeRouteParameter('foo', route, navigation)

expect(param).toBe(0)
expect(navigation.setParams).toHaveBeenCalled()
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(navigation.setParams.mock.calls[0][0]).toStrictEqual({
foo: undefined
})
Expand Down
22 changes: 14 additions & 8 deletions src/libs/functions/session.spec.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import AsyncStorage from '@react-native-async-storage/async-storage'

import { createMockClient } from 'cozy-client/dist/mock'
import type CozyClient from 'cozy-client'
// @ts-expect-error : cozy-client has to be updated
import type { StackClient } from 'cozy-stack-client'

import { makeSessionAPI } from './session'

import strings from '/constants/strings.json'

const session_code = '123'
const uri = 'http://cozy.10-0-2-2.nip.io:8080'
const client = createMockClient({})
const client = {} as jest.Mocked<CozyClient>

const subdomain = 'nested'

client.getStackClient = jest.fn(() => ({
fetchSessionCode: () => Promise.resolve({ session_code }),
uri
}))
client.getStackClient = jest.fn(
(): StackClient => ({
fetchSessionCode: (): Promise<{ session_code: string }> =>
Promise.resolve({ session_code }),
uri
})
)

const {
shouldCreateSession,
Expand All @@ -27,12 +33,12 @@ const {

describe('shouldCreateSession', () => {
it('returns true when no token is found', async () => {
AsyncStorage.clear()
void AsyncStorage.clear()
expect(await shouldCreateSession()).toBe(true)
})

it('returns false when a token is found', async () => {
AsyncStorage.setItem(strings.SESSION_CREATED_FLAG, '1')
void AsyncStorage.setItem(strings.SESSION_CREATED_FLAG, '1')
expect(await shouldCreateSession()).toBe(false)
})
})
Expand Down
Loading

0 comments on commit 50b5ed7

Please sign in to comment.