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.
- package/README.md +1 -1
- package/package.json +4 -4
- package/src/__tests__/blackbox-parity.test.ts +45 -26
- package/src/__tests__/blackbox-plugin-state.test.ts +25 -6
- package/src/__tests__/blind/plugin-deps/doctor.test.ts +622 -0
- package/src/__tests__/blind/plugin-deps/helpers.ts +340 -0
- package/src/__tests__/blind/plugin-deps/hook.test.ts +328 -0
- package/src/__tests__/blind/plugin-deps/probe.test.ts +694 -0
- package/src/__tests__/blind/plugin-deps/read-only.test.ts +96 -0
- package/src/__tests__/blind/plugin-deps/schema.test.ts +390 -0
- package/src/__tests__/blind-780-cli-tools.test.ts +796 -0
- package/src/__tests__/blind-780-deprecated.test.ts +631 -0
- package/src/__tests__/blind-780-failure-reasons.test.ts +1004 -0
- package/src/__tests__/blind-780-plugin-resolution.test.ts +431 -0
- package/src/__tests__/blind-780-review2.test.ts +1178 -0
- package/src/__tests__/cli-apply-seams.test.ts +2 -67
- package/src/__tests__/cli-tool-commands.test.ts +131 -6
- package/src/__tests__/cli-tools-adapter.test.ts +52 -2
- package/src/__tests__/cli-tools-plugin-managed.test.tsx +83 -0
- package/src/__tests__/cli-update-view.test.ts +3 -3
- package/src/__tests__/deprecated-remove.test.ts +827 -0
- package/src/__tests__/deps-adapter.test.ts +191 -0
- package/src/__tests__/deps-check-hook.test.ts +220 -0
- package/src/__tests__/deps-plan.test.ts +896 -0
- package/src/__tests__/deps-report.test.ts +197 -0
- package/src/__tests__/doctor-bins.test.ts +134 -36
- package/src/__tests__/doctor-json.test.ts +261 -0
- package/src/__tests__/doctor.test.ts +159 -1
- package/src/__tests__/failure-popup-review2.test.tsx +896 -0
- package/src/__tests__/failure-popup.test.tsx +419 -0
- package/src/__tests__/final-fixes-780.test.tsx +740 -0
- package/src/__tests__/fixtures/bins.ts +55 -0
- package/src/__tests__/format-update-outcome.test.ts +6 -32
- package/src/__tests__/gitignore-profile.test.ts +1 -9
- package/src/__tests__/install-plan.test.ts +15 -1
- package/src/__tests__/marketplace-refresh.test.ts +9 -3
- package/src/__tests__/models-screen-state.test.ts +18 -40
- package/src/__tests__/no-shell-spawn.test.ts +77 -0
- package/src/__tests__/npm-package-name.test.ts +6 -9
- package/src/__tests__/plugin-requires.test.ts +138 -153
- package/src/__tests__/plugin-resolution.test.ts +52 -13
- package/src/__tests__/plugin-row-text.test.ts +30 -37
- package/src/__tests__/profile-dependencies.test.ts +7 -0
- package/src/__tests__/profile-startup-old-format.test.ts +0 -3
- package/src/__tests__/profile-startup.test.ts +40 -12
- package/src/__tests__/requires-probe.test.ts +858 -0
- package/src/__tests__/resolver.test.ts +129 -52
- package/src/__tests__/run.test.ts +108 -0
- package/src/__tests__/stale-load-repair.test.ts +31 -23
- package/src/__tests__/tool-probe.test.ts +1 -1
- package/src/__tests__/toolchain.test.ts +123 -52
- package/src/__tests__/update-apply.test.ts +13 -29
- package/src/__tests__/update-plan.test.ts +24 -18
- package/src/__tests__/useStaleLoadModal.test.ts +1 -57
- package/src/__tests__/version-unknown-state.test.ts +15 -17
- package/src/cli/ansi.ts +2 -7
- package/src/cli/deps-view.ts +213 -0
- package/src/cli/doctor.ts +320 -54
- package/src/cli/install.ts +55 -71
- package/src/cli/profile.ts +20 -2
- package/src/cli/router.ts +6 -2
- package/src/cli/update-view.ts +58 -15
- package/src/cli/update.ts +55 -12
- package/src/data/cli-tools.ts +24 -9
- package/src/services/active-closure.ts +3 -3
- package/src/services/catalog-cache-store.ts +8 -6
- package/src/services/claude-cli.ts +106 -19
- package/src/services/claude-settings.ts +88 -384
- package/src/services/cli-tool-commands.ts +104 -3
- package/src/services/deps-plan.ts +962 -0
- package/src/services/deps-report.ts +265 -0
- package/src/services/doctor-bins.ts +115 -43
- package/src/services/install-plan.ts +11 -1
- package/src/services/local-marketplace.ts +12 -206
- package/src/services/marketplace-refresh.ts +4 -4
- package/src/services/plugin-env-needs.ts +69 -0
- package/src/services/plugin-manager.ts +35 -37
- package/src/services/plugin-requires.ts +93 -162
- package/src/services/plugin-resolution.ts +34 -42
- package/src/services/profile-activate.ts +1 -2
- package/src/services/profile-startup.ts +18 -43
- package/src/services/profile-state.ts +72 -2
- package/src/services/resolver.ts +263 -59
- package/src/services/tool-probe.ts +18 -25
- package/src/services/toolchain.ts +117 -111
- package/src/services/update-engine.ts +345 -45
- package/src/services/update-plan.ts +296 -36
- package/src/shared/plugin/hooks/bun-missing.sh +39 -0
- package/src/shared/plugin/hooks/deps-check.ts +224 -0
- package/src/shared/plugin/hooks/lib/requires-probe.ts +1183 -0
- package/src/types/index.ts +60 -16
- package/src/ui/App.tsx +81 -171
- package/src/ui/adapters/cliToolsAdapter.ts +252 -12
- package/src/ui/adapters/depsAdapter.ts +129 -0
- package/src/ui/adapters/failureReport.ts +137 -0
- package/src/ui/adapters/pluginsAdapter.ts +297 -45
- package/src/ui/adapters/startupReports.ts +260 -0
- package/src/ui/components/modals/FailureListModal.tsx +231 -0
- package/src/ui/components/modals/MessageModal.tsx +7 -12
- package/src/ui/components/modals/ModalContainer.tsx +10 -5
- package/src/ui/components/modals/index.ts +1 -0
- package/src/ui/hooks/startupQueue.ts +56 -0
- package/src/ui/hooks/useGitignoreModal.ts +30 -52
- package/src/ui/hooks/useProfileStartupModal.ts +119 -142
- package/src/ui/hooks/useStaleLoadModal.ts +35 -65
- package/src/ui/keymap.ts +14 -1
- package/src/ui/renderers/cliToolRenderers.tsx +135 -2
- package/src/ui/renderers/mcpRenderers.tsx +110 -3
- package/src/ui/renderers/pluginRenderers.tsx +77 -6
- package/src/ui/renderers/pluginRowText.ts +25 -25
- package/src/ui/renderers/pluginStatusText.ts +18 -1
- package/src/ui/screens/AliasScreen.tsx +38 -45
- package/src/ui/screens/CliToolsScreen.tsx +199 -98
- package/src/ui/screens/EnvVarsScreen.tsx +17 -5
- package/src/ui/screens/GitignoreScreen.tsx +48 -67
- package/src/ui/screens/McpRegistryScreen.tsx +67 -38
- package/src/ui/screens/McpScreen.tsx +46 -19
- package/src/ui/screens/ModelsScreen.tsx +46 -86
- package/src/ui/screens/PluginsScreen.tsx +315 -259
- package/src/ui/screens/ProfilesScreen.tsx +79 -40
- package/src/ui/screens/SkillsScreen.tsx +83 -80
- package/src/ui/screens/StylesScreen.tsx +211 -275
- package/src/ui/state/AppContext.tsx +66 -44
- package/src/ui/state/DimensionsContext.tsx +3 -22
- package/src/ui/state/reducer.ts +12 -100
- package/src/ui/state/types.ts +48 -69
- package/src/utils/command-utils.ts +4 -5
- package/src/utils/run.ts +123 -52
- package/src/utils/string-utils.ts +9 -0
- package/src/utils/user-path.ts +60 -0
- package/src/__tests__/go-module.test.ts +0 -43
- package/src/__tests__/notices-rules.test.ts +0 -476
- package/src/__tests__/notices.test.tsx +0 -604
- package/src/__tests__/plugin-setup.test.ts +0 -1016
- package/src/__tests__/shell-script-callers.test.ts +0 -92
- package/src/__tests__/styles-status-line.test.ts +0 -72
- package/src/__tests__/version-snapshot.test.ts +0 -212
- package/src/services/plugin-setup.ts +0 -965
- package/src/services/version-snapshot.ts +0 -189
- 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
|
-
});
|