⚠️ Черновой вариант документации. Может содержать опечатки и неточности.
EN | DE | RU | FR | ES

Комментарий: git diff origin/develop -- reducers/authentication.test.js

Дата: 2026-05-08

Сравниваемые коммиты:

Редьюсер (старый и новый — идентичен): reducers/authentication.js (40 строк) — не менялся между коммитами (создан 2019-02-28).

Примечание: Ссылки на новый код (SHA e67067aa7) ведут на форк MaurerAnton/projectforge (ветка draft43npm). Ссылки на старый код (SHA 9ed5fbe0f) — на основной репозиторий micromata/projectforge (develop).

Секция 1: Initial state (строки 1–10)

@@ -1,10 +1,10 @@
1 import reducer from './authentication';
2
3 const exampleError = 'Uncool error message.';
4
5 Object.freeze(exampleError);
6+
7+const initialState = {
8+ loading: true,
9+ error: null,
10+ user: null,
11+};
6
7 describe('reducer', () => {
8 it('initial state', () => {
9 expect(reducer(undefined, {}))
10- .toEqual({
11- loading: false,
12- error: null,
13- loggedIn: false,
14- });
+ .toEqual(initialState);

1. initialState вынесен в константу.

Было: объект захардкожен в тесте it('initial state') как { loading: false, error: null, loggedIn: false }.

Стало: константа initialState, которая к тому же совпадает с initialState из самого редьюсера [reducers/authentication.js:3].

2. loading: falseloading: true.

Старый init: loading: false — пользователь не логинился, спиннер не крутится.

Новый init: loading: true — при загрузке приложения сразу идёт loadUserStatus(), который проверяет сессию. Стартовое состояние — «ищем пользователя». Это изменение редьюсера commit 8b3b44be7 (2020-03-18).

3. loggedIn: falseuser: null. ГЛАВНОЕ ИЗМЕНЕНИЕ STATE SHAPE.

{/* Старая форма */}
{ loading: boolean, error: string|null, loggedIn: boolean }
{/* Новая форма */}
{ loading: boolean, error: string|null, user: object|null }

Секция 2: Unknown action (строки 11–21)

@@ -12,19 +12,14 @@
17- it('unknown action', () => {
13+ it('unknown action returns current state', () => {
18 const state = {
19- loading: true,
20- error: null,
21- loggedIn: false,
+ loading: false,
+ error: null,
+ user: { name: 'test' },
22 };
23
24 Object.freeze(state);
25
26- expect(reducer(state, {
27- type: 'UNKNOWN_ACTION',
28- }))
+ expect(reducer(state, { type: 'UNKNOWN_ACTION' }))
29 .toEqual(state);
30 });
31 });

unknown actionunknown action returns current state. Описание явное: «редиректор не знает такой action → возвращает текущее состояние без изменений». Старый тест не говорил что именно проверяет.

Изменение входного state: старый тест проверял { loading: true, loggedIn: false }, новый — { loading: false, user: { name: 'test' } }. Выбран state залогиненного пользователя, чтобы проверить что user не зануляется при неизвестном action — гарантия чистой функции [Redux reducers].

Форматирование: старый вызов разбит на 3 строки, новый — одной строкой. Только стиль.

Object.freeze(state) [MDN] — защита от мутации: если reducer изменит переданный state (нарушит чистоту), тест упадёт с ошибкой.

Секция 3: USER_LOGIN_BEGIN — удалены 2 теста, 1 изменён (строки 22–62)

@@ -33,57 +28,40 @@
33-describe('handles USER_LOGIN_BEGIN', () => {
34- it('fresh state', () => {
35- const state = {
36- loading: false,
37- error: null,
38- loggedIn: false,
39- };
40-
41- Object.freeze(state);
42-
43- expect(reducer(state, {
44- type: 'USER_LOGIN_BEGIN',
45- }))
46- .toEqual({
47- loading: true,
48- error: null,
49- loggedIn: false,
50- });
51- });
52-
53- it('error state', () => {
54- const state = {
55- loading: false,
56- error: exampleError,
57- loggedIn: false,
58- };
59-
60- Object.freeze(state);
61-
62- expect(reducer(state, {
63- type: 'USER_LOGIN_BEGIN',
64- }))
65- .toEqual({
66- loading: true,
67- error: null,
68- loggedIn: false,
69- });
70- });
71-
72- it('loggedIn state', () => {
73- const state = {
74- loading: false,
75- error: null,
76- loggedIn: false,
77- };
78-
79- Object.freeze(state);
80-
81- expect(reducer(state, {
82- type: 'USER_LOGIN_BEGIN',
83- }))
84- .toEqual({
85- loading: true,
86- error: null,
87- loggedIn: false,
88- });
89- });
90-});

describe('handles USER_LOGIN_BEGIN')describe('USER_LOGIN_BEGIN'). Убрано слово handles — в новом стиле все describe называют action дословно. [новый файл]

Удалено 2 из 3 тестов.

«fresh state» — проверял переход из { loading: false, error: null, ... }{ loading: true, error: null, ... }. [строка 34]

«error state» — проверял переход из { loading: false, error: exampleError, ... }{ loading: true, error: null, ... }. [строка 53]

Оба теста проверяют одно и то же: error зануляется, loading становится true. Разница — в исходном error (null vs строка). Избыточно — один тест покрывает оба случая.

«loggedIn state» — вход: { loading: false, error: null, loggedIn: false } — полностью совпадает с «fresh state». Возможно, опечатка (должно было быть loggedIn: true). [строка 72]

Один новый тест: 'resets to loading with null user' [новый файл]

Секция 4: USER_LOGIN_SUCCESS — удалён 1 тест, 1 изменён (строки 63–109)

@@ -91,29 +61,18 @@
92-describe('handles USER_LOGIN_SUCCESS', () => {
93- it('loading state', () => {
+describe('USER_LOGIN_SUCCESS', () => {
+ it('sets user and clears loading/error', () => {
94 const state = {
95 loading: true,
96 error: null,
97- loggedIn: false,
+ user: null,
98 };
99
100 Object.freeze(state);
101
102 expect(reducer(state, {
103 type: 'USER_LOGIN_SUCCESS',
+ payload,
106 }))
107
108- .toEqual({
109- loading: false,
110- error: null,
111- loggedIn: true,
112- });
113- });
114-
115- it('weird state', () => {
116- const state = {
117- loading: false,
118- error: exampleError,
119- loggedIn: true,
120- };
121-
122- Object.freeze(state);
123-
124- expect(reducer(state, {
125- type: 'USER_LOGIN_SUCCESS',
126- }))
127- .toEqual({
128- loading: false,
129- error: null,
130- loggedIn: true,
131- });
132- });
133-});

Старый тест «loading state» — БЕЗ PAYLOAD.

Вызывал { type: 'USER_LOGIN_SUCCESS' } без третьего аргумента (payload). Старый редьюсер якобы просто ставил loggedIn: true и loading: false — пользовательские данные не сохранялись.

Удалён «weird state»: { loading: false, error: exampleError, loggedIn: true } — бессмысленная комбинация: error есть при loggedIn = true. [строка 115]

Новый тест — с реальным payload:

const payload = {
  user: { name: 'testuser' },
  version: '1.0',
  buildTimestamp: '2024-01-01',
  alertMessage: 'Welcome',
};

Почему именно эти поля: редьюсер в authentication.js:18-27 ожидает payload с четырьмя полями — каждое пишется в state напрямую:

Поле payload→ поле stateСтрокаЗачем
payload.userstate.userL23объект пользователя (имя, админ?)
payload.versionstate.versionL24версия сборки
payload.buildTimestampstate.buildTimestampL25когда собрано
payload.alertMessagestate.alertMessageL26системное сообщение (MOTD)

Старый тест не передавал payload вообще — а значит не проверял ни одного из этих полей. Новый тест проверяет полное соответствие.

loggedIn: trueuser: { name: 'testuser' }. Компоненты проверяют state.user !== null вместо state.loggedIn === true — часть миграции на react-redux hooks.

Секция 5: USER_LOGIN_FAILURE — удалён 1 тест, 1 изменён (строки 110–148)

@@ -133,47 +94,30 @@
133-describe('handles USER_LOGIN_FAILURE', () => {
134- it('loading state', () => {
+describe('USER_LOGIN_FAILURE', () => {
+ it('sets error and clears user/loading', () => {
135 const state = {
136 loading: true,
137 error: null,
138- loggedIn: false,
+ user: { name: 'old' },
139 };
140
141 Object.freeze(state);
142
143 expect(reducer(state, {
144 type: 'USER_LOGIN_FAILURE',
145- payload: {
146- error: exampleError,
147- },
+ payload: { error: exampleError },
148 }))
149 .toEqual({
150 loading: false,
151 error: exampleError,
152- loggedIn: false,
+ user: null,
153 });
154- });
155-
156- it('weird state', () => {
157- const state = {
158- loading: false,
159- error: exampleError,
160- loggedIn: true,
161- };
162-
163- Object.freeze(state);
164-
165- expect(reducer(state, {
166- type: 'USER_LOGIN_FAILURE',
167- payload: {
168- error: exampleError,
169- },
170- }))
171- .toEqual({
172- loading: false,
173- error: exampleError,
174- loggedIn: false,
175- });
176- });
177-});

Аналогичные изменения: handles USER_LOGIN_FAILUREUSER_LOGIN_FAILURE, удалён «weird state», один тест вместо двух.

Новое описание: 'sets error and clears user/loading' — явно говорит что происходит: ошибка сохраняется, пользователь сбрасывается, спиннер убирается.

loggedIn: falseuser: null. Поле user становится null при ошибке. Это жёстче чем loggedIn: false — старый подход оставлял пользовательские данные в state (просто помечал что не залогинен), новый — полностью очищает. Соответствует case USER_LOGIN_FAILURE: user: null.

Форматирование payload: { error: exampleError } на одной строке вместо трёх. Только стиль.

Секция 6: Удалён describe('handles USER_LOGOUT') (строки 178–216)

@@ -178,39 +120,12 @@
178-describe('handles USER_LOGOUT', () => {
179- const expectedState = {
180- loading: false,
181- error: null,
182- loggedIn: false,
183- };
184-
185- Object.freeze(expectedState);
186-
187- it('logged in state', () => {
188- const state = {
189- loading: false,
190- error: null,
191- loggedIn: true,
192- };
193-
194- Object.freeze(state);
195-
196- expect(reducer(state, {
197- type: 'USER_LOGOUT',
198- }))
199- .toEqual(expectedState);
200- });
201-
202- it('weird state', () => {
203- const state = {
204- loading: true,
205- error: exampleError,
206- loggedIn: false,
207- };
208-
209- Object.freeze(state);
210-
211- expect(reducer(state, {
212- type: 'USER_LOGOUT',
213- }))
214- .toEqual(expectedState);
215- });
216-});

Удалён целиком (2 теста, ~40 строк).

Проверял что USER_LOGOUT сбрасывает { loggedIn: true } в { loggedIn: false } и зануляет ошибку.

Почему удалён:

  1. Action USER_LOGOUT больше не существует — не экспортится из authentication.js (импортит только USER_LOGIN_BEGIN, USER_LOGIN_FAILURE, USER_LOGIN_SUCCESS).
  2. USER_LOGOUT нет в switch редьюсера [только 3 case].
  3. Функциональность логаута перекрывается USER_LOGIN_BEGIN — оба сбрасывают состояние.
  4. Тест импортировал USER_LOGOUT из несуществующего экспорта — тест был сломан.

Исторически: USER_LOGOUT был удалён из исходников commit 7c60c2fbb (2019-07-13, «implemented logout check») — из actions/authentication.js и из редьюсера. Но тест не обновили — он продолжал импортировать и тестировать USER_LOGOUT ещё 7 лет. В новом редьюсере логаут — это просто USER_LOGIN_BEGIN (сброс к loading).

Понятия

Redux reducer
Чистая функция: (state, action) → new state. Никаких side effects, никаких мутаций. Object.freeze(state) в тесте гарантирует это: если редьюсер случайно мутирует переданный state, тест упадёт. [Redux docs]
State shape: loggedIn → user
Старый state: { loading, error, loggedIn }. Новый: { loading, error, user }. loggedIn: boolean говорил только «да/нет». user: object|null даёт полный объект пользователя (id, name, locale, ...) — компоненты через useSelector(state => state.authentication.user) получают сразу все данные.
Object.freeze
Делает объект неизменяемым. В тесте редьюсера: Object.freeze(state); reducer(state, action); — если редьюсер попытается изменить state (вместо создания нового), JavaScript бросит ошибку в strict mode. Это проверка чистоты функции. [MDN]

Сводка изменений

АспектСтарый файл (216 строк)Новый файл (98 строк)
State shape { loading, error, loggedIn } { loading, error, user }
Initial state Встроен в тест, loading: false Константа, совпадает с редьюсером, loading: true
Количество describe 5 (с префиксом handles) 3 (без префикса)
Количество тестов 9 4
USER_LOGIN_BEGIN 3 теста (fresh, error, loggedIn — дубликаты) 1 тест (resets to loading)
USER_LOGIN_SUCCESS 2 теста — без payload 1 тест — с payload (user, version и т.д.)
USER_LOGIN_FAILURE 2 теста (loading, weird) 1 тест (sets error)
USER_LOGOUT 2 теста Удалён полностью — action не существует
Описание unknown action 'unknown action' — непонятно 'unknown action returns current state' — понятно

Почему 9 тестов → 4?

  1. Три describe по 1 тесту — каждый action проверяется один раз с репрезентативными входными данными. Не нужно тестировать «свежее», «с ошибкой» и «залогинен» для каждого action.
  2. USER_LOGOUT удалён — его функциональность покрыта USER_LOGIN_BEGIN (оба сбрасывают состояние). Action не существует в редьюсере.
  3. USER_LOGIN_SUCCESS тестирует payload — старый тест не проверял данные пользователя (user, version, buildTimestamp, alertMessage), новый проверяет все поля.
  4. State shape изменёнloggedIn: boolean заменён на user: object|null как часть миграции connect()useSelector [react-redux hooks].

— ссылка на код в ветке draft43npm на форке, не слитой в develop. Код может быть не доступен на GitHub до слияния PR.