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

EcrituresNumeriques / stylo / 14886870075

07 May 2025 03:10PM UTC coverage: 37.615% (+0.03%) from 37.581%
14886870075

push

github

web-flow
Honore l'identifiant de workspace lors de l'initialisation de l'application (#1487)

Co-authored-by: Thomas Parisot <thom4parisot@users.noreply.github.com>

532 of 759 branches covered (70.09%)

Branch coverage included in aggregate %.

2 of 34 new or added lines in 10 files covered. (5.88%)

2 existing lines in 1 file now uncovered.

5240 of 14586 relevant lines covered (35.92%)

2.51 hits per line

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

56.09
/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
  },
1✔
42
  activeWorkspaceId: null,
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
      ...user,
×
171
    },
×
172
  }
×
173
}
×
174

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

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

198
  return state
1✔
199
}
1✔
200

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

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

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

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

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

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

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

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

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

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

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

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

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

291
function setActiveWorkspace(state, { workspaceId }) {
×
292
  return {
×
293
    ...state,
×
NEW
294
    activeWorkspaceId: workspaceId,
×
295
  }
×
296
}
×
297

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

311
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
1✔
312
  ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
1!
313
      trace: true,
×
314
      traceLimit: 25,
×
315
    })
×
316
  : compose
1✔
317

318
export default function createReduxStore(state = {}) {
1✔
319
  return createStore(
26✔
320
    createRootReducer({
26✔
321
      ...structuredClone(initialState),
26✔
322
      ...structuredClone(state),
26✔
323
    }),
26✔
324
    composeEnhancers(
26✔
325
      applyMiddleware(persistStateIntoLocalStorage),
26✔
326
      sentryReduxEnhancer
26✔
327
    )
26✔
328
  )
26✔
329
}
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