• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

EcrituresNumeriques / stylo / 21433867219

28 Jan 2026 10:06AM UTC coverage: 69.336% (-0.5%) from 69.865%
21433867219

Pull #1870

github

web-flow
Merge 2ecb40001 into 3e2a2d1b3
Pull Request #1870: Redesign de l'écran articles

731 of 1151 branches covered (63.51%)

Branch coverage included in aggregate %.

30 of 107 new or added lines in 4 files covered. (28.04%)

36 existing lines in 2 files now uncovered.

4013 of 5691 relevant lines covered (70.51%)

5.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

20.93
/front/src/hooks/user.js
1
import { useCallback, useMemo, useState } from 'react'
2
import { useTranslation } from 'react-i18next'
3
import { useDispatch, useSelector } from 'react-redux'
4
import { useRouteLoaderData } from 'react-router'
5

6
import { applicationConfig } from '../config.js'
7
import { useGraphQLClient } from '../helpers/graphQL.js'
8
import useFetchData, { useMutateData } from './graphql.js'
9

10
import { logoutMutation, unsetAuthTokenMutation } from './Credentials.graphql'
11
import { createTag, getTags, updateTag } from './Tag.graphql'
12

13
/**
14
 * @returns {string|null}
15
 */
16
export function useActiveUserId() {
17
  const { user } = useRouteLoaderData('app')
×
18
  return user?._id
×
19
}
20

21
/**
22
 * @typedef {import('redux').Dispatch} Dispatch
23
 */
24

25
/**
26
 *
27
 * @param {string} key
28
 * @param {'article' | 'user' | 'export' | 'corpus' } namespace
29
 * @returns {{ value: string|boolean|number, setValue: Dispatch, toggleValue: Dispatch }}
30
 */
31
export function usePreferenceItem(key, namespace = 'article') {
×
32
  const dispatch = useDispatch()
×
33
  const value = useSelector((state) => state[`${namespace}Preferences`]?.[key])
×
34

35
  const ns = namespace.toUpperCase()
×
36

37
  return {
×
38
    value,
39
    /**
40
     * @param {boolean | undefined} value
41
     */
42
    setValue(value) {
43
      dispatch({ type: `SET_${ns}_PREFERENCES`, key, value })
×
44
    },
45
    toggleValue() {
46
      dispatch({ type: `${ns}_PREFERENCES_TOGGLE`, key })
×
47
    },
48
  }
49
}
50

51
/**
52
 *
53
 * @param {'humanid' | 'hypothesis' | 'zotero' } service
54
 * @returns {{ link: React.EffectCallback, token: string | undefined, id: string | undefined, isLinked: boolean, error: string, unlink: React.EffectCallback }}
55
 */
56
export function useSetAuthToken(service) {
57
  const dispatch = useDispatch()
×
58
  const { query } = useGraphQLClient()
×
59
  const [error, setError] = useState('')
×
60
  const { backendEndpoint, frontendEndpoint } = applicationConfig
×
61

62
  const token = useSelector(
×
63
    (state) => state.activeUser.authProviders?.[service]?.token
×
64
  )
65

66
  const id = useSelector(
×
67
    (state) => state.activeUser.authProviders?.[service]?.id
×
68
  )
69

70
  const isLinked = useMemo(() => id ?? token, [id, token])
×
71

72
  const link = useCallback(async function handleSetAuthToken() {
×
73
    setError('')
×
74
    const popup = window.open(
×
75
      `${backendEndpoint}/authorize/${service}?returnTo=${frontendEndpoint}/credentials/auth-callback/${service}`,
76
      `auth-${service}`,
77
      'width=660&height=360&menubar=0&toolbar=0'
78
    )
79

80
    async function handleClose({ data, type, source }) {
81
      if (source === popup && type === 'message' && data) {
×
82
        const authProviders = JSON.parse(data ?? '')
×
83

84
        if (authProviders) {
×
85
          dispatch({
×
86
            type: 'UPDATE_ACTIVE_USER_DETAILS',
87
            payload: {
88
              authProviders,
89
            },
90
          })
91
        }
92

93
        popup.close()
×
94
      }
95
    }
96

97
    window.addEventListener('message', handleClose)
×
98
    popup.addEventListener('beforeunload', () =>
×
99
      window.removeEventListener('message', handleClose)
×
100
    )
101
  }, [])
102

103
  const unlink = useCallback(async () => {
×
104
    try {
×
105
      setError('')
×
106
      const { unsetAuthToken } = await query({
×
107
        query: unsetAuthTokenMutation,
108
        variables: { service },
109
      })
110

111
      dispatch({ type: 'UPDATE_ACTIVE_USER_DETAILS', payload: unsetAuthToken })
×
112
    } catch (error) {
113
      setError(error.messages.at(0).extensions.type)
×
114
    }
115
  }, [])
116

117
  return { link, unlink, token, id, isLinked, error }
×
118
}
119

120
export function useUserTagActions() {
121
  const { query } = useGraphQLClient()
3✔
122
  const { mutate } = useMutateData({
3✔
123
    query: getTags,
124
    variables: {},
125
  })
126
  const create = async (tag) => {
3✔
127
    const result = await query({
1✔
128
      query: createTag,
129
      variables: tag,
130
      type: 'mutation',
131
    })
132
    await mutate(
1✔
133
      async (data) => {
134
        const tags = data?.user?.tags ?? []
1✔
135
        return {
1✔
136
          user: {
137
            tags: [...tags, result.createTag],
138
          },
139
        }
140
      },
141
      { revalidate: false }
142
    )
143
    return result.createTag
1✔
144
  }
145
  const update = async (tag) => {
3✔
146
    const result = await query({
1✔
147
      query: updateTag,
148
      variables: tag,
149
      type: 'mutation',
150
    })
151
    await mutate(
1✔
152
      async (data) => ({
1✔
153
        user: {
154
          tags: data.user.tags.map((tag) => {
155
            return tag._id === result.updateTag._id ? result.updateTag : tag
1!
156
          }),
157
        },
158
      }),
159
      { revalidate: false }
160
    )
161
    return result.updateTag
1✔
162
  }
163

164
  return {
3✔
165
    create,
166
    update,
167
  }
168
}
169

170
export function useUserTags() {
171
  const { data, error, isLoading } = useFetchData({
×
172
    query: getTags,
173
    variables: {},
174
  })
175

176
  const tags = data?.user?.tags || []
×
177
  return {
×
178
    tags,
179
    error,
180
    isLoading,
181
  }
182
}
183

184
export function useLogout() {
185
  const dispatch = useDispatch()
×
186
  const { query } = useGraphQLClient()
×
187

188
  return useCallback(async () => {
×
189
    await query({ query: logoutMutation })
×
190

191
    dispatch({ type: 'LOGOUT' })
×
192
  }, [])
193
}
194

195
/**
196
 * @returns {(user: User) => string}
197
 */
198
export function useDisplayName() {
NEW
199
  const { t } = useTranslation()
×
200

NEW
201
  return function displayName(user = {}) {
×
UNCOV
202
    if (user.deletedAt) {
×
UNCOV
203
      return t('user.account.isDeleted.displayName')
×
204
    }
205

206
    return user.displayName || user.username
×
207
  }
208
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc