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

EcrituresNumeriques / stylo / 14737333824

29 Apr 2025 05:17PM UTC coverage: 37.741% (+0.1%) from 37.635%
14737333824

push

github

web-flow
fix: le nombre de mots quand le texte est vide (#1467)

532 of 760 branches covered (70.0%)

Branch coverage included in aggregate %.

21 of 22 new or added lines in 2 files covered. (95.45%)

3 existing lines in 1 file now uncovered.

5239 of 14531 relevant lines covered (36.05%)

2.52 hits per line

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

55.38
/front/src/createReduxStore.js
1
import * as Sentry from '@sentry/react'
1✔
2
import { applyMiddleware, compose, createStore } from 'redux'
1✔
3

4
import { computeTextStats } from './helpers/markdown.js'
1✔
5

6
const sentryReduxEnhancer = Sentry.createReduxEnhancer()
1✔
7
const sessionTokenName = 'sessionToken'
1✔
8

9
// Définition du store Redux et de l'ensemble des actions
1✔
10
export const initialState = {
1✔
11
  sessionToken: localStorage.getItem(sessionTokenName),
1✔
12
  articleWorkingCopy: {
1✔
13
    status: 'synced',
1✔
14
  },
1✔
15
  articleStructure: [],
1✔
16
  articleWriters: [],
1✔
17
  articlePreferences: localStorage.getItem('articlePreferences')
1✔
18
    ? JSON.parse(localStorage.getItem('articlePreferences'))
1!
19
    : {
1✔
20
        expandSidebarLeft: true,
1✔
21
        expandSidebarRight: false,
1✔
22
        metadataFormMode: 'basic',
1✔
23
        expandVersions: false,
1✔
24
      },
1✔
25
  articleFilters: {
1✔
26
    tagIds: [],
1✔
27
    text: '',
1✔
28
  },
1✔
29
  articleStats: {
1✔
30
    wordCount: 0,
1✔
31
    charCountNoSpace: 0,
1✔
32
    charCountPlusSpace: 0,
1✔
33
    citationNb: 0,
1✔
34
  },
1✔
35
  // Active user (authenticated)
1✔
36
  activeUser: {
1✔
37
    authTypes: [],
1✔
38
    authProviders: {},
1✔
39
    selectedTagIds: [],
1✔
40
    workspaces: [],
1✔
41
    activeWorkspaceId: null,
1✔
42
  },
1✔
43
  userPreferences: localStorage.getItem('userPreferences')
1✔
44
    ? JSON.parse(localStorage.getItem('userPreferences'))
1!
45
    : {
1✔
46
        // The user we impersonate
1✔
47
        currentUser: null,
1✔
48
        trackingConsent: true /* default value should be false */,
1✔
49
      },
1✔
50
  exportPreferences: localStorage.getItem('exportPreferences')
1✔
51
    ? JSON.parse(localStorage.getItem('exportPreferences'))
1!
52
    : {
1✔
53
        bibliography_style: 'chicagomodified',
1✔
54
        with_toc: 0,
1✔
55
        link_citations: 0,
1✔
56
        with_nocite: 0,
1✔
57
        formats: 'html',
1✔
58
        unnumbered: 0,
1✔
59
        book_division: 'part',
1✔
60
      },
1✔
61
  editorCursorPosition: {
1✔
62
    lineNumber: 0,
1✔
63
    column: 0,
1✔
64
  },
1✔
65
}
1✔
66

67
/**
1✔
68
 *
1✔
69
 * @param {*} state
1✔
70
 * @param initialState
1✔
71
 * @param handlers
1✔
72
 * @returns
1✔
73
 */
1✔
74
function createReducer(initialState, handlers) {
26✔
75
  return function reducer(state = initialState, action) {
26✔
76
    if (Object.prototype.hasOwnProperty.call(handlers, action.type)) {
29✔
77
      return handlers[action.type](state, action)
3✔
78
    } else {
29✔
79
      return state
26✔
80
    }
26✔
81
  }
29✔
82
}
26✔
83

84
/**
1✔
85
 *
1✔
86
 * @param {*} state
1✔
87
 * @returns
1✔
88
 */
1✔
89
function createRootReducer(state) {
26✔
90
  return createReducer(state, {
26✔
91
    PROFILE: setProfile,
26✔
92
    LOGIN: loginUser,
26✔
93
    UPDATE_SESSION_TOKEN: setSessionToken,
26✔
94
    UPDATE_ACTIVE_USER_DETAILS: updateActiveUserDetails,
26✔
95
    LOGOUT: logoutUser,
26✔
96

97
    // article reducers
26✔
98
    UPDATE_ARTICLE_STATS: updateArticleStats,
26✔
99
    UPDATE_ARTICLE_STRUCTURE: updateArticleStructure,
26✔
100
    UPDATE_ARTICLE_WRITERS: updateArticleWriters,
26✔
101
    UPDATE_ARTICLE_WORKING_COPY_STATUS: updateArticleWorkingCopyStatus,
26✔
102

103
    // user preferences reducers
26✔
104
    USER_PREFERENCES_TOGGLE: toggleUserPreferences,
26✔
105
    SET_EXPORT_PREFERENCES: setExportPreferences,
26✔
106

107
    ARTICLE_PREFERENCES_TOGGLE: toggleArticlePreferences,
26✔
108

109
    UPDATE_EDITOR_CURSOR_POSITION: updateEditorCursorPosition,
26✔
110

111
    SET_ACTIVE_WORKSPACE: setActiveWorkspace,
26✔
112

113
    UPDATE_SELECTED_TAG: updateSelectedTag,
26✔
114
  })
26✔
115
}
26✔
116

117
function persistStateIntoLocalStorage({ getState }) {
26✔
118
  const actionStateMap = new Map([
26✔
119
    ['ARTICLE_PREFERENCES_TOGGLE', 'articlePreferences'],
26✔
120
    ['USER_PREFERENCES_TOGGLE', 'userPreferences'],
26✔
121
    ['SET_EXPORT_PREFERENCES', 'exportPreferences'],
26✔
122
  ])
26✔
123

124
  return (next) => {
26✔
125
    return (action) => {
26✔
126
      if (actionStateMap.has(action.type)) {
3!
127
        const key = actionStateMap.get(action.type)
×
128
        // we run the reducer first
×
129
        next(action)
×
130
        // we fetch the updated state
×
131
        const state = getState()[key]
×
132

133
        // we persist it for a later page reload
×
134
        localStorage.setItem(key, JSON.stringify(state))
×
135

136
        return
×
137
      } else if (action.type === 'LOGOUT') {
3!
138
        localStorage.removeItem('articlePreferences')
×
139
        localStorage.removeItem('userPreferences')
×
140
      }
×
141

142
      if (action.type === 'LOGIN' || action.type === 'UPDATE_SESSION_TOKEN') {
3✔
143
        next(action)
1✔
144
        const { sessionToken } = getState()
1✔
145
        localStorage.setItem(sessionTokenName, sessionToken)
1✔
146
        return
1✔
147
      }
1✔
148

149
      if (action.type === 'LOGOUT') {
2!
150
        localStorage.removeItem(sessionTokenName)
×
151
        return next(action)
×
152
      }
×
153

154
      return next(action)
2✔
155
    }
3✔
156
  }
26✔
157
}
26✔
158

159
function setProfile(state, action) {
×
160
  const { user } = action
×
161

162
  if (!user) {
×
163
    return { ...state, activeUser: structuredClone(initialState.activeUser) }
×
164
  }
×
165

166
  return {
×
167
    ...state,
×
168
    activeUser: {
×
169
      ...state.activeUser,
×
170
      activeWorkspaceId: action.activeWorkspaceId,
×
171
      ...user,
×
172
    },
×
173
  }
×
174
}
×
175

176
function setSessionToken(state, { token: sessionToken }) {
×
177
  return {
×
178
    ...state,
×
179
    sessionToken,
×
180
  }
×
181
}
×
182

183
function loginUser(state, { user, token: sessionToken }) {
1✔
184
  if (sessionToken) {
1!
185
    Sentry.setUser({ id: user._id })
×
186
    return {
×
187
      ...state,
×
188
      sessionToken,
×
189
      activeUser: {
×
190
        ...state.activeUser,
×
191
        ...user,
×
192
        // dates are expected to be in timestamp string format (including milliseconds)
×
193
        createdAt: String(new Date(user.createdAt).getTime()),
×
194
        updatedAt: String(new Date(user.updatedAt).getTime()),
×
195
      },
×
196
    }
×
197
  }
×
198

199
  return state
1✔
200
}
1✔
201

202
function updateActiveUserDetails(state, action) {
2✔
203
  return {
2✔
204
    ...state,
2✔
205
    activeUser: { ...state.activeUser, ...action.payload },
2✔
206
  }
2✔
207
}
2✔
208

209
function logoutUser() {
×
210
  return structuredClone(initialState)
×
211
}
×
212

UNCOV
213
function updateArticleStats(state, { md }) {
×
UNCOV
214
  return {
×
UNCOV
215
    ...state,
×
NEW
216
    articleStats: computeTextStats(md),
×
217
  }
×
218
}
×
219

220
function updateArticleStructure(state, { md }) {
×
221
  const text = (md || '').trim()
×
222
  const articleStructure = text
×
223
    .split('\n')
×
224
    .map((line, index) => ({ line, index }))
×
225
    .filter((lineWithIndex) => lineWithIndex.line.match(/^##+ /))
×
226
    .map((lineWithIndex) => {
×
227
      const title = lineWithIndex.line
×
228
        .replace(/##/, '')
×
229
        //arrow backspace (\u21B3)
×
230
        .replace(/#\s/g, '\u21B3')
×
231
        // middle dot (\u00B7) + non-breaking space (\xa0)
×
232
        .replace(/#/g, '\u00B7\xa0')
×
233
      return { ...lineWithIndex, title }
×
234
    })
×
235

236
  return { ...state, articleStructure }
×
237
}
×
238

239
function updateArticleWriters(state, { articleWriters }) {
×
240
  return { ...state, articleWriters }
×
241
}
×
242

243
function updateArticleWorkingCopyStatus(state, { status }) {
×
244
  return {
×
245
    ...state,
×
246
    articleWorkingCopy: { ...state.articleWorkingCopy, status },
×
247
  }
×
248
}
×
249

250
function togglePreferences(storeKey) {
40✔
251
  return function togglePreferencesReducer(state, { key, value }) {
40✔
252
    const preferences = state[storeKey]
×
253

254
    return {
×
255
      ...state,
×
256
      [storeKey]: {
×
257
        ...preferences,
×
258
        [key]: value === undefined ? !preferences[key] : value,
×
259
      },
×
260
    }
×
261
  }
×
262
}
40✔
263

264
function setPreferences(storeKey) {
20✔
265
  return function setPreferencesReducer(state, { key, value }) {
20✔
266
    const preferences = state[storeKey]
×
267

268
    return {
×
269
      ...state,
×
270
      [storeKey]: {
×
271
        ...preferences,
×
272
        [key]: value,
×
273
      },
×
274
    }
×
275
  }
×
276
}
20✔
277

278
const toggleArticlePreferences = togglePreferences('articlePreferences')
1✔
279
const toggleUserPreferences = togglePreferences('userPreferences')
1✔
280
const setExportPreferences = setPreferences('exportPreferences')
1✔
281

282
function updateEditorCursorPosition(state, { lineNumber, column }) {
×
283
  return {
×
284
    ...state,
×
285
    editorCursorPosition: {
×
286
      lineNumber,
×
287
      column,
×
288
    },
×
289
  }
×
290
}
×
291

292
function setActiveWorkspace(state, { workspaceId }) {
×
293
  return {
×
294
    ...state,
×
295
    activeUser: {
×
296
      ...state.activeUser,
×
297
      activeWorkspaceId: workspaceId,
×
298
    },
×
299
  }
×
300
}
×
301

302
function updateSelectedTag(state, { tagId }) {
×
303
  const { selectedTagIds } = state.activeUser
×
304
  return {
×
305
    ...state,
×
306
    activeUser: {
×
307
      ...state.activeUser,
×
308
      selectedTagIds: selectedTagIds.includes(tagId)
×
309
        ? selectedTagIds.filter((selectedTagId) => selectedTagId !== tagId)
×
310
        : [...selectedTagIds, tagId],
×
311
    },
×
312
  }
×
313
}
×
314

315
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
1✔
316
  ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
1!
317
      trace: true,
×
318
      traceLimit: 25,
×
319
    })
×
320
  : compose
1✔
321

322
export default function createReduxStore(state = {}) {
1✔
323
  return createStore(
26✔
324
    createRootReducer({
26✔
325
      ...structuredClone(initialState),
26✔
326
      ...structuredClone(state),
26✔
327
    }),
26✔
328
    composeEnhancers(
26✔
329
      applyMiddleware(persistStateIntoLocalStorage),
26✔
330
      sentryReduxEnhancer
26✔
331
    )
26✔
332
  )
26✔
333
}
26✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc