magus-cli 7.7.0 → 7.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -4
  3. package/src/__tests__/blackbox-parity.test.ts +45 -26
  4. package/src/__tests__/blackbox-plugin-state.test.ts +25 -6
  5. package/src/__tests__/blind/plugin-deps/doctor.test.ts +622 -0
  6. package/src/__tests__/blind/plugin-deps/helpers.ts +340 -0
  7. package/src/__tests__/blind/plugin-deps/hook.test.ts +328 -0
  8. package/src/__tests__/blind/plugin-deps/probe.test.ts +694 -0
  9. package/src/__tests__/blind/plugin-deps/read-only.test.ts +96 -0
  10. package/src/__tests__/blind/plugin-deps/schema.test.ts +390 -0
  11. package/src/__tests__/blind-780-cli-tools.test.ts +796 -0
  12. package/src/__tests__/blind-780-deprecated.test.ts +631 -0
  13. package/src/__tests__/blind-780-failure-reasons.test.ts +1004 -0
  14. package/src/__tests__/blind-780-plugin-resolution.test.ts +431 -0
  15. package/src/__tests__/blind-780-review2.test.ts +1178 -0
  16. package/src/__tests__/cli-apply-seams.test.ts +2 -67
  17. package/src/__tests__/cli-tool-commands.test.ts +131 -6
  18. package/src/__tests__/cli-tools-adapter.test.ts +52 -2
  19. package/src/__tests__/cli-tools-plugin-managed.test.tsx +83 -0
  20. package/src/__tests__/cli-update-view.test.ts +3 -3
  21. package/src/__tests__/deprecated-remove.test.ts +827 -0
  22. package/src/__tests__/deps-adapter.test.ts +191 -0
  23. package/src/__tests__/deps-check-hook.test.ts +220 -0
  24. package/src/__tests__/deps-plan.test.ts +896 -0
  25. package/src/__tests__/deps-report.test.ts +197 -0
  26. package/src/__tests__/doctor-bins.test.ts +134 -36
  27. package/src/__tests__/doctor-json.test.ts +261 -0
  28. package/src/__tests__/doctor.test.ts +159 -1
  29. package/src/__tests__/failure-popup-review2.test.tsx +896 -0
  30. package/src/__tests__/failure-popup.test.tsx +419 -0
  31. package/src/__tests__/final-fixes-780.test.tsx +740 -0
  32. package/src/__tests__/fixtures/bins.ts +55 -0
  33. package/src/__tests__/format-update-outcome.test.ts +6 -32
  34. package/src/__tests__/gitignore-profile.test.ts +1 -9
  35. package/src/__tests__/install-plan.test.ts +15 -1
  36. package/src/__tests__/marketplace-refresh.test.ts +9 -3
  37. package/src/__tests__/models-screen-state.test.ts +18 -40
  38. package/src/__tests__/no-shell-spawn.test.ts +77 -0
  39. package/src/__tests__/npm-package-name.test.ts +6 -9
  40. package/src/__tests__/plugin-requires.test.ts +138 -153
  41. package/src/__tests__/plugin-resolution.test.ts +52 -13
  42. package/src/__tests__/plugin-row-text.test.ts +30 -37
  43. package/src/__tests__/profile-dependencies.test.ts +7 -0
  44. package/src/__tests__/profile-startup-old-format.test.ts +0 -3
  45. package/src/__tests__/profile-startup.test.ts +40 -12
  46. package/src/__tests__/requires-probe.test.ts +858 -0
  47. package/src/__tests__/resolver.test.ts +129 -52
  48. package/src/__tests__/run.test.ts +108 -0
  49. package/src/__tests__/stale-load-repair.test.ts +31 -23
  50. package/src/__tests__/tool-probe.test.ts +1 -1
  51. package/src/__tests__/toolchain.test.ts +123 -52
  52. package/src/__tests__/update-apply.test.ts +13 -29
  53. package/src/__tests__/update-plan.test.ts +24 -18
  54. package/src/__tests__/useStaleLoadModal.test.ts +1 -57
  55. package/src/__tests__/version-unknown-state.test.ts +15 -17
  56. package/src/cli/ansi.ts +2 -7
  57. package/src/cli/deps-view.ts +213 -0
  58. package/src/cli/doctor.ts +320 -54
  59. package/src/cli/install.ts +55 -71
  60. package/src/cli/profile.ts +20 -2
  61. package/src/cli/router.ts +6 -2
  62. package/src/cli/update-view.ts +58 -15
  63. package/src/cli/update.ts +55 -12
  64. package/src/data/cli-tools.ts +24 -9
  65. package/src/services/active-closure.ts +3 -3
  66. package/src/services/catalog-cache-store.ts +8 -6
  67. package/src/services/claude-cli.ts +106 -19
  68. package/src/services/claude-settings.ts +88 -384
  69. package/src/services/cli-tool-commands.ts +104 -3
  70. package/src/services/deps-plan.ts +962 -0
  71. package/src/services/deps-report.ts +265 -0
  72. package/src/services/doctor-bins.ts +115 -43
  73. package/src/services/install-plan.ts +11 -1
  74. package/src/services/local-marketplace.ts +12 -206
  75. package/src/services/marketplace-refresh.ts +4 -4
  76. package/src/services/plugin-env-needs.ts +69 -0
  77. package/src/services/plugin-manager.ts +35 -37
  78. package/src/services/plugin-requires.ts +93 -162
  79. package/src/services/plugin-resolution.ts +34 -42
  80. package/src/services/profile-activate.ts +1 -2
  81. package/src/services/profile-startup.ts +18 -43
  82. package/src/services/profile-state.ts +72 -2
  83. package/src/services/resolver.ts +263 -59
  84. package/src/services/tool-probe.ts +18 -25
  85. package/src/services/toolchain.ts +117 -111
  86. package/src/services/update-engine.ts +345 -45
  87. package/src/services/update-plan.ts +296 -36
  88. package/src/shared/plugin/hooks/bun-missing.sh +39 -0
  89. package/src/shared/plugin/hooks/deps-check.ts +224 -0
  90. package/src/shared/plugin/hooks/lib/requires-probe.ts +1183 -0
  91. package/src/types/index.ts +60 -16
  92. package/src/ui/App.tsx +81 -171
  93. package/src/ui/adapters/cliToolsAdapter.ts +252 -12
  94. package/src/ui/adapters/depsAdapter.ts +129 -0
  95. package/src/ui/adapters/failureReport.ts +137 -0
  96. package/src/ui/adapters/pluginsAdapter.ts +297 -45
  97. package/src/ui/adapters/startupReports.ts +260 -0
  98. package/src/ui/components/modals/FailureListModal.tsx +231 -0
  99. package/src/ui/components/modals/MessageModal.tsx +7 -12
  100. package/src/ui/components/modals/ModalContainer.tsx +10 -5
  101. package/src/ui/components/modals/index.ts +1 -0
  102. package/src/ui/hooks/startupQueue.ts +56 -0
  103. package/src/ui/hooks/useGitignoreModal.ts +30 -52
  104. package/src/ui/hooks/useProfileStartupModal.ts +119 -142
  105. package/src/ui/hooks/useStaleLoadModal.ts +35 -65
  106. package/src/ui/keymap.ts +14 -1
  107. package/src/ui/renderers/cliToolRenderers.tsx +135 -2
  108. package/src/ui/renderers/mcpRenderers.tsx +110 -3
  109. package/src/ui/renderers/pluginRenderers.tsx +77 -6
  110. package/src/ui/renderers/pluginRowText.ts +25 -25
  111. package/src/ui/renderers/pluginStatusText.ts +18 -1
  112. package/src/ui/screens/AliasScreen.tsx +38 -45
  113. package/src/ui/screens/CliToolsScreen.tsx +199 -98
  114. package/src/ui/screens/EnvVarsScreen.tsx +17 -5
  115. package/src/ui/screens/GitignoreScreen.tsx +48 -67
  116. package/src/ui/screens/McpRegistryScreen.tsx +67 -38
  117. package/src/ui/screens/McpScreen.tsx +46 -19
  118. package/src/ui/screens/ModelsScreen.tsx +46 -86
  119. package/src/ui/screens/PluginsScreen.tsx +315 -259
  120. package/src/ui/screens/ProfilesScreen.tsx +79 -40
  121. package/src/ui/screens/SkillsScreen.tsx +83 -80
  122. package/src/ui/screens/StylesScreen.tsx +211 -275
  123. package/src/ui/state/AppContext.tsx +66 -44
  124. package/src/ui/state/DimensionsContext.tsx +3 -22
  125. package/src/ui/state/reducer.ts +12 -100
  126. package/src/ui/state/types.ts +48 -69
  127. package/src/utils/command-utils.ts +4 -5
  128. package/src/utils/run.ts +123 -52
  129. package/src/utils/string-utils.ts +9 -0
  130. package/src/utils/user-path.ts +60 -0
  131. package/src/__tests__/go-module.test.ts +0 -43
  132. package/src/__tests__/notices-rules.test.ts +0 -476
  133. package/src/__tests__/notices.test.tsx +0 -604
  134. package/src/__tests__/plugin-setup.test.ts +0 -1016
  135. package/src/__tests__/shell-script-callers.test.ts +0 -92
  136. package/src/__tests__/styles-status-line.test.ts +0 -72
  137. package/src/__tests__/version-snapshot.test.ts +0 -212
  138. package/src/services/plugin-setup.ts +0 -965
  139. package/src/services/version-snapshot.ts +0 -189
  140. package/src/utils/go-module.ts +0 -36
@@ -1,476 +0,0 @@
1
- /**
2
- * Notice rules: eviction order, pausing under a modal, the unchanged-plugin summary,
3
- * and what PROFILE_ACTIVATED asks of the app.
4
- *
5
- * Written blind: from the spec and the exported signatures and types only
6
- * (`appReducer`, `initialState`, `MAX_NOTICES`, `NOTICE_DURATION_MS`, the AppAction
7
- * union and AppState in ui/state/types.ts, `noMoveReasons()` and the `ApplyReporter`
8
- * interface it hands back). No function body was used to decide an expectation.
9
- *
10
- * Coverage map (requirement → describe block):
11
- * REQ-A1 5th distinct notice drops the oldest SUCCESS → "REQ-A eviction"
12
- * REQ-A2 a warning goes only when every line is a warning → "REQ-A eviction"
13
- * REQ-A3 a new success is itself dropped among 4 warnings → "REQ-A eviction"
14
- * REQ-B1 EXPIRE_NOTICES does nothing while paused → "REQ-B pause"
15
- * REQ-B2 resume gives each line back the time it was held → "REQ-B pause"
16
- * REQ-B3 ... capped at its full duration from resume → "REQ-B pause"
17
- * REQ-B4 a line posted while paused gets its full duration → "REQ-B pause"
18
- * REQ-C1 0 unchanged → no notice → "REQ-C noMoveReasons"
19
- * REQ-C2 1 unchanged → `No change: <id>: <reason>` → "REQ-C noMoveReasons"
20
- * REQ-C3 several, same reason → one line with the reason → "REQ-C noMoveReasons"
21
- * REQ-C4 several, different reasons → one line, no reason → "REQ-C noMoveReasons"
22
- * REQ-D PROFILE_ACTIVATED bumps both counters by one → "REQ-D PROFILE_ACTIVATED"
23
- *
24
- * Known gaps:
25
- * REQ-C tone ("warning"): `notice()` returns a bare string, so the tone is chosen
26
- * by the screen that posts it; the contract gives no pure seam to observe it.
27
- * REQ-B "while a modal is open": which component dispatches PAUSE/RESUME is not in
28
- * the contract, so only the reducer half is tested.
29
- *
30
- * Reasons reach `noMoveReasons` through its reporter: the contract says the engine
31
- * reports the cause as the finish detail `no change — <why>` for an item whose
32
- * installer exited 0, so the tests call `reporter.finish(true, name, detail, ms)`.
33
- */
34
- import { describe, expect, test } from "bun:test";
35
- import { noMoveReasons } from "../ui/adapters/pluginsAdapter.js";
36
- import {
37
- MAX_NOTICES,
38
- NOTICE_DURATION_MS,
39
- appReducer,
40
- initialState,
41
- } from "../ui/state/reducer.js";
42
- import type {
43
- AppAction,
44
- AppState,
45
- ModalState,
46
- Notice,
47
- NoticeTone,
48
- } from "../ui/state/types.js";
49
-
50
- // ---------------------------------------------------------------------------
51
- // Builders
52
- // ---------------------------------------------------------------------------
53
-
54
- let nextId = 1;
55
- function notice(
56
- text: string,
57
- tone: NoticeTone = "success",
58
- expiresAt = 1_000_000,
59
- ): Notice {
60
- return { id: nextId++, text, tone, expiresAt };
61
- }
62
-
63
- function stateWith(notices: Notice[] = []): AppState {
64
- return { ...initialState, notices };
65
- }
66
-
67
- function run(state: AppState, ...actions: AppAction[]): AppState {
68
- return actions.reduce(appReducer, state);
69
- }
70
-
71
- function notifyAll(state: AppState, notices: Notice[]): AppState {
72
- return run(
73
- state,
74
- ...notices.map((n): AppAction => ({ type: "NOTIFY", notice: n })),
75
- );
76
- }
77
-
78
- function texts(state: AppState): string[] {
79
- return state.notices.map((n) => n.text);
80
- }
81
-
82
- // ---------------------------------------------------------------------------
83
- // REQ-A eviction
84
- // ---------------------------------------------------------------------------
85
-
86
- describe("REQ-A eviction", () => {
87
- test("REQ-A the banner holds at most MAX_NOTICES (4) lines", () => {
88
- expect(MAX_NOTICES).toBe(4);
89
-
90
- const next = notifyAll(
91
- stateWith(),
92
- ["a", "b", "c", "d", "e", "f"].map((t) => notice(t)),
93
- );
94
-
95
- expect(next.notices).toHaveLength(MAX_NOTICES);
96
- });
97
-
98
- test("REQ-A1 a fifth warning drops the only success, not the oldest warning", () => {
99
- const lines = [
100
- notice("warn-1", "warning"),
101
- notice("warn-2", "warning"),
102
- notice("ok-1", "success"),
103
- notice("warn-3", "warning"),
104
- ];
105
-
106
- const next = notifyAll(stateWith(), [
107
- ...lines,
108
- notice("warn-4", "warning"),
109
- ]);
110
-
111
- expect(texts(next)).toEqual(["warn-1", "warn-2", "warn-3", "warn-4"]);
112
- });
113
-
114
- test("REQ-A1 of two successes, the OLDER success is the one dropped", () => {
115
- const next = notifyAll(stateWith(), [
116
- notice("warn-1", "warning"),
117
- notice("ok-old", "success"),
118
- notice("ok-new", "success"),
119
- notice("warn-2", "warning"),
120
- notice("ok-latest", "success"),
121
- ]);
122
-
123
- expect(texts(next)).toEqual(["warn-1", "ok-new", "warn-2", "ok-latest"]);
124
- });
125
-
126
- test("REQ-A1 a success that is the oldest line overall is dropped first", () => {
127
- const next = notifyAll(stateWith(), [
128
- notice("ok-1", "success"),
129
- notice("warn-1", "warning"),
130
- notice("ok-2", "success"),
131
- notice("warn-2", "warning"),
132
- notice("warn-3", "warning"),
133
- ]);
134
-
135
- expect(texts(next)).toEqual(["warn-1", "ok-2", "warn-2", "warn-3"]);
136
- });
137
-
138
- test("REQ-A2 when every line is a warning, the oldest warning is dropped", () => {
139
- const next = notifyAll(stateWith(), [
140
- notice("warn-1", "warning"),
141
- notice("warn-2", "warning"),
142
- notice("warn-3", "warning"),
143
- notice("warn-4", "warning"),
144
- notice("warn-5", "warning"),
145
- ]);
146
-
147
- expect(texts(next)).toEqual(["warn-2", "warn-3", "warn-4", "warn-5"]);
148
- });
149
-
150
- test("REQ-A3 a new success among four warnings is itself the line dropped", () => {
151
- const warnings = [
152
- notice("warn-1", "warning"),
153
- notice("warn-2", "warning"),
154
- notice("warn-3", "warning"),
155
- notice("warn-4", "warning"),
156
- ];
157
-
158
- const next = notifyAll(stateWith(), [...warnings, notice("ok-new")]);
159
-
160
- expect(next.notices).toEqual(warnings);
161
- });
162
-
163
- test("REQ-A a repeated text at the cap evicts nothing else", () => {
164
- const next = notifyAll(stateWith(), [
165
- notice("ok-1", "success"),
166
- notice("warn-1", "warning"),
167
- notice("ok-2", "success"),
168
- notice("warn-2", "warning"),
169
- notice("ok-1", "success"),
170
- ]);
171
-
172
- expect([...texts(next)].sort()).toEqual([
173
- "ok-1",
174
- "ok-2",
175
- "warn-1",
176
- "warn-2",
177
- ]);
178
- });
179
- });
180
-
181
- // ---------------------------------------------------------------------------
182
- // REQ-B pause
183
- // ---------------------------------------------------------------------------
184
-
185
- const PAUSED_AT = 1_000_000;
186
- const RESUMED_AT = PAUSED_AT + 60_000;
187
-
188
- describe("REQ-B pause", () => {
189
- test("REQ-B1 EXPIRE_NOTICES removes nothing while paused, even long past expiry", () => {
190
- const a = notice("ok", "success", PAUSED_AT + 2_000);
191
- const b = notice("careful", "warning", PAUSED_AT + 5_000);
192
-
193
- const next = run(
194
- stateWith([a, b]),
195
- { type: "PAUSE_NOTICES", now: PAUSED_AT },
196
- { type: "EXPIRE_NOTICES", now: PAUSED_AT + 500_000 },
197
- );
198
-
199
- expect(next.notices).toEqual([a, b]);
200
- });
201
-
202
- test("REQ-B1 PAUSE_NOTICES records when the pause began; RESUME_NOTICES clears it", () => {
203
- const paused = run(stateWith(), { type: "PAUSE_NOTICES", now: PAUSED_AT });
204
- const resumed = run(paused, { type: "RESUME_NOTICES", now: RESUMED_AT });
205
-
206
- expect(paused.noticesPausedAt).toBe(PAUSED_AT);
207
- expect(resumed.noticesPausedAt).toBeNull();
208
- });
209
-
210
- test("REQ-B2 a line with 2 s left at pause still has 2 s after resume", () => {
211
- const held = notice("ok", "success", PAUSED_AT + 2_000);
212
-
213
- const resumed = run(
214
- stateWith([held]),
215
- { type: "PAUSE_NOTICES", now: PAUSED_AT },
216
- { type: "RESUME_NOTICES", now: RESUMED_AT },
217
- );
218
-
219
- expect(resumed.notices).toHaveLength(1);
220
- expect(resumed.notices[0].expiresAt).toBe(RESUMED_AT + 2_000);
221
- });
222
-
223
- test("REQ-B2 after resume the line survives just before its held time and is gone just after", () => {
224
- const held = notice("careful", "warning", PAUSED_AT + 3_000);
225
- const resumed = run(
226
- stateWith([held]),
227
- { type: "PAUSE_NOTICES", now: PAUSED_AT },
228
- { type: "RESUME_NOTICES", now: RESUMED_AT },
229
- );
230
-
231
- const before = run(resumed, {
232
- type: "EXPIRE_NOTICES",
233
- now: RESUMED_AT + 2_999,
234
- });
235
- const after = run(resumed, {
236
- type: "EXPIRE_NOTICES",
237
- now: RESUMED_AT + 3_001,
238
- });
239
-
240
- expect(texts(before)).toEqual(["careful"]);
241
- expect(texts(after)).toEqual([]);
242
- });
243
-
244
- test("REQ-B2 each line keeps its own remaining time", () => {
245
- const soon = notice("soon", "success", PAUSED_AT + 1_000);
246
- const later = notice("later", "warning", PAUSED_AT + 7_000);
247
-
248
- const resumed = run(
249
- stateWith([soon, later]),
250
- { type: "PAUSE_NOTICES", now: PAUSED_AT },
251
- { type: "RESUME_NOTICES", now: RESUMED_AT },
252
- );
253
-
254
- expect(resumed.notices.map((n) => [n.text, n.expiresAt])).toEqual([
255
- ["soon", RESUMED_AT + 1_000],
256
- ["later", RESUMED_AT + 7_000],
257
- ]);
258
- });
259
-
260
- test("REQ-B2 timers run again after resume", () => {
261
- const held = notice("ok", "success", PAUSED_AT + 2_000);
262
-
263
- const next = run(
264
- stateWith([held]),
265
- { type: "PAUSE_NOTICES", now: PAUSED_AT },
266
- { type: "RESUME_NOTICES", now: RESUMED_AT },
267
- { type: "EXPIRE_NOTICES", now: RESUMED_AT + 100_000 },
268
- );
269
-
270
- expect(next.notices).toEqual([]);
271
- });
272
-
273
- test("REQ-B3 a success held with more than its full duration is capped at the success duration", () => {
274
- const long = notice("ok", "success", PAUSED_AT + 50_000);
275
-
276
- const resumed = run(
277
- stateWith([long]),
278
- { type: "PAUSE_NOTICES", now: PAUSED_AT },
279
- { type: "RESUME_NOTICES", now: RESUMED_AT },
280
- );
281
-
282
- expect(resumed.notices[0].expiresAt).toBe(
283
- RESUMED_AT + NOTICE_DURATION_MS.success,
284
- );
285
- });
286
-
287
- test("REQ-B3 a warning is capped at the warning duration, not the success one", () => {
288
- const long = notice("careful", "warning", PAUSED_AT + 500_000);
289
-
290
- const resumed = run(
291
- stateWith([long]),
292
- { type: "PAUSE_NOTICES", now: PAUSED_AT },
293
- { type: "RESUME_NOTICES", now: RESUMED_AT },
294
- );
295
-
296
- expect(resumed.notices[0].expiresAt).toBe(
297
- RESUMED_AT + NOTICE_DURATION_MS.warning,
298
- );
299
- });
300
-
301
- test("REQ-B4 a success posted during the pause gets its full duration counted from resume", () => {
302
- const postedAt = PAUSED_AT + 30_000;
303
- const posted = notice(
304
- "posted under a modal",
305
- "success",
306
- postedAt + NOTICE_DURATION_MS.success,
307
- );
308
-
309
- const resumed = run(
310
- stateWith(),
311
- { type: "PAUSE_NOTICES", now: PAUSED_AT },
312
- { type: "NOTIFY", notice: posted },
313
- { type: "EXPIRE_NOTICES", now: RESUMED_AT - 1 },
314
- { type: "RESUME_NOTICES", now: RESUMED_AT },
315
- );
316
-
317
- expect(texts(resumed)).toEqual(["posted under a modal"]);
318
- expect(resumed.notices[0].expiresAt).toBe(
319
- RESUMED_AT + NOTICE_DURATION_MS.success,
320
- );
321
- });
322
-
323
- test("REQ-B4 a warning posted during the pause gets the full warning duration from resume", () => {
324
- const postedAt = PAUSED_AT + 10;
325
- const posted = notice(
326
- "act on this",
327
- "warning",
328
- postedAt + NOTICE_DURATION_MS.warning,
329
- );
330
-
331
- const resumed = run(
332
- stateWith(),
333
- { type: "PAUSE_NOTICES", now: PAUSED_AT },
334
- { type: "NOTIFY", notice: posted },
335
- { type: "RESUME_NOTICES", now: RESUMED_AT },
336
- );
337
-
338
- expect(resumed.notices[0].expiresAt).toBe(
339
- RESUMED_AT + NOTICE_DURATION_MS.warning,
340
- );
341
- });
342
- });
343
-
344
- // ---------------------------------------------------------------------------
345
- // REQ-C noMoveReasons
346
- // ---------------------------------------------------------------------------
347
-
348
- /** Report `name` as the engine does for an item that ran and did not move. */
349
- function reportNoMove(
350
- collector: ReturnType<typeof noMoveReasons>,
351
- name: string,
352
- why: string,
353
- ): void {
354
- collector.reporter.begin(name);
355
- collector.reporter.finish(true, name, `no change — ${why}`, 0);
356
- }
357
-
358
- describe("REQ-C noMoveReasons", () => {
359
- test("REQ-C1 nothing unchanged gives no notice", () => {
360
- const c = noMoveReasons();
361
-
362
- expect(c.notice([])).toBeNull();
363
- });
364
-
365
- test("REQ-C1 nothing unchanged gives no notice even when a reason was reported", () => {
366
- const c = noMoveReasons();
367
- reportNoMove(c, "dev@magus", "already at the newest version");
368
-
369
- expect(c.notice([])).toBeNull();
370
- });
371
-
372
- test("REQ-C2 one unchanged plugin is `No change: <id>: <reason>`", () => {
373
- const c = noMoveReasons();
374
- reportNoMove(c, "dev@magus", "already at the newest version");
375
-
376
- expect(c.notice(["dev@magus"])).toBe(
377
- "No change: dev@magus: already at the newest version",
378
- );
379
- });
380
-
381
- test("REQ-C3 several unchanged with the same reason are ONE line with count, names and reason", () => {
382
- const c = noMoveReasons();
383
- for (const name of ["alpha", "beta", "gamma"]) {
384
- reportNoMove(c, name, "marketplace not refreshed");
385
- }
386
-
387
- const line = c.notice(["alpha", "beta", "gamma"]);
388
-
389
- expect(line).toBe(
390
- "3 plugins unchanged — alpha, beta, gamma: marketplace not refreshed",
391
- );
392
- });
393
-
394
- test("REQ-C3 the count follows the number of unchanged plugins", () => {
395
- const c = noMoveReasons();
396
- reportNoMove(c, "alpha", "pinned");
397
- reportNoMove(c, "beta", "pinned");
398
-
399
- expect(c.notice(["alpha", "beta"])).toBe(
400
- "2 plugins unchanged — alpha, beta: pinned",
401
- );
402
- });
403
-
404
- test("REQ-C4 several unchanged with different reasons are ONE line with no reason", () => {
405
- const c = noMoveReasons();
406
- reportNoMove(c, "alpha", "marketplace not refreshed");
407
- reportNoMove(c, "beta", "pinned");
408
- reportNoMove(c, "gamma", "marketplace not refreshed");
409
-
410
- const line = c.notice(["alpha", "beta", "gamma"]);
411
-
412
- expect(line).toBe("3 plugins unchanged — alpha, beta, gamma");
413
- });
414
-
415
- test("REQ-C the summary is a single line", () => {
416
- const c = noMoveReasons();
417
- reportNoMove(c, "alpha", "one");
418
- reportNoMove(c, "beta", "two");
419
-
420
- const line = c.notice(["alpha", "beta"]);
421
-
422
- expect(line).not.toBeNull();
423
- expect(line).not.toContain("\n");
424
- });
425
- });
426
-
427
- // ---------------------------------------------------------------------------
428
- // REQ-D PROFILE_ACTIVATED
429
- // ---------------------------------------------------------------------------
430
-
431
- describe("REQ-D PROFILE_ACTIVATED", () => {
432
- const modal: ModalState = { type: "loading", message: "Working" };
433
-
434
- function activeState(): AppState {
435
- return {
436
- ...initialState,
437
- dataRefreshVersion: 7,
438
- gitignoreCheckRequest: 3,
439
- notices: [notice("Saved"), notice("careful", "warning")],
440
- modal,
441
- };
442
- }
443
-
444
- test("REQ-D bumps dataRefreshVersion by one", () => {
445
- const next = appReducer(activeState(), { type: "PROFILE_ACTIVATED" });
446
-
447
- expect(next.dataRefreshVersion).toBe(8);
448
- });
449
-
450
- test("REQ-D bumps gitignoreCheckRequest by one", () => {
451
- const next = appReducer(activeState(), { type: "PROFILE_ACTIVATED" });
452
-
453
- expect(next.gitignoreCheckRequest).toBe(4);
454
- });
455
-
456
- test("REQ-D twice bumps both by two", () => {
457
- const next = run(
458
- activeState(),
459
- { type: "PROFILE_ACTIVATED" },
460
- { type: "PROFILE_ACTIVATED" },
461
- );
462
-
463
- expect([next.dataRefreshVersion, next.gitignoreCheckRequest]).toEqual([
464
- 9, 5,
465
- ]);
466
- });
467
-
468
- test("REQ-D leaves the notices and the modal unchanged", () => {
469
- const state = activeState();
470
-
471
- const next = appReducer(state, { type: "PROFILE_ACTIVATED" });
472
-
473
- expect(next.notices).toEqual(state.notices);
474
- expect(next.modal).toBe(modal);
475
- });
476
- });