github-issue-tower-defence-management 2.6.1 → 2.8.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 (121) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +0 -2
  3. package/bin/adapter/entry-points/cli/index.js +3 -6
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/cli/projectConfig.js +0 -3
  6. package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
  7. package/bin/adapter/entry-points/console/consoleAirplaneSnapshotApi.js +347 -0
  8. package/bin/adapter/entry-points/console/consoleAirplaneSnapshotApi.js.map +1 -0
  9. package/bin/adapter/entry-points/console/ui-dist/assets/index-DkMqSpkk.js +88 -0
  10. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  11. package/bin/adapter/entry-points/console/webServer.js +28 -0
  12. package/bin/adapter/entry-points/console/webServer.js.map +1 -1
  13. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +1 -3
  14. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  15. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js +4 -2
  16. package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js.map +1 -1
  17. package/bin/domain/usecases/HandleScheduledEventUseCase.js +1 -6
  18. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  19. package/bin/domain/usecases/IssueRejectionEvaluator.js +0 -23
  20. package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
  21. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +39 -69
  22. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  23. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +3 -2
  24. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
  25. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +0 -43
  26. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  27. package/bin/domain/usecases/autoStatusCheckComments.js +1 -11
  28. package/bin/domain/usecases/autoStatusCheckComments.js.map +1 -1
  29. package/bin/domain/usecases/extractNextStepAgentFromComments.js +14 -0
  30. package/bin/domain/usecases/extractNextStepAgentFromComments.js.map +1 -0
  31. package/bin/domain/usecases/triagerAgentName.js +7 -0
  32. package/bin/domain/usecases/triagerAgentName.js.map +1 -0
  33. package/jest.config.js +1 -0
  34. package/package.json +1 -1
  35. package/src/adapter/entry-points/cli/index.test.ts +0 -2
  36. package/src/adapter/entry-points/cli/index.ts +4 -10
  37. package/src/adapter/entry-points/cli/projectConfig.ts +0 -8
  38. package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.test.ts +658 -0
  39. package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.ts +482 -0
  40. package/src/adapter/entry-points/console/ui/jest-globals.cjs +26 -0
  41. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.test.tsx +10 -0
  42. package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.tsx +23 -16
  43. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.stories.tsx +56 -0
  44. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.test.tsx +111 -0
  45. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.tsx +92 -0
  46. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +15 -0
  47. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +19 -0
  48. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.tsx +30 -1
  49. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleErrorToast.stories.tsx +10 -0
  50. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.test.tsx +1 -3
  51. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.tsx +8 -7
  52. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.test.ts +304 -0
  53. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.ts +167 -0
  54. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.test.ts +15 -0
  55. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.ts +6 -1
  56. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.test.ts +77 -0
  57. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +48 -3
  58. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.test.ts +51 -0
  59. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.ts +39 -0
  60. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +215 -0
  61. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +20 -1
  62. package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.test.ts +361 -0
  63. package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.ts +401 -0
  64. package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +4 -0
  65. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +209 -0
  66. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +27 -2
  67. package/src/adapter/entry-points/console/ui-dist/assets/index-DkMqSpkk.js +88 -0
  68. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  69. package/src/adapter/entry-points/console/webServer.test.ts +189 -0
  70. package/src/adapter/entry-points/console/webServer.ts +38 -0
  71. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +0 -7
  72. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +26 -4
  73. package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +5 -3
  74. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +0 -28
  75. package/src/domain/usecases/HandleScheduledEventUseCase.ts +0 -6
  76. package/src/domain/usecases/IssueRejectionEvaluator.test.ts +0 -119
  77. package/src/domain/usecases/IssueRejectionEvaluator.ts +0 -21
  78. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +185 -327
  79. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +68 -138
  80. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +4 -4
  81. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +11 -5
  82. package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +0 -313
  83. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +1 -102
  84. package/src/domain/usecases/autoStatusCheckComments.ts +0 -17
  85. package/src/domain/usecases/extractNextStepAgentFromComments.ts +13 -0
  86. package/src/domain/usecases/isHumanComment.test.ts +0 -13
  87. package/src/domain/usecases/triagerAgentName.ts +4 -0
  88. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  89. package/types/adapter/entry-points/cli/projectConfig.d.ts +0 -1
  90. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  91. package/types/adapter/entry-points/console/consoleAirplaneSnapshotApi.d.ts +90 -0
  92. package/types/adapter/entry-points/console/consoleAirplaneSnapshotApi.d.ts.map +1 -0
  93. package/types/adapter/entry-points/console/webServer.d.ts +1 -0
  94. package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
  95. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  96. package/types/domain/usecases/CheckIssueReviewReadinessUseCase.d.ts.map +1 -1
  97. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +1 -3
  98. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  99. package/types/domain/usecases/IssueRejectionEvaluator.d.ts +0 -1
  100. package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
  101. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +0 -1
  102. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  103. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
  104. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts +0 -1
  105. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  106. package/types/domain/usecases/autoStatusCheckComments.d.ts +0 -4
  107. package/types/domain/usecases/autoStatusCheckComments.d.ts.map +1 -1
  108. package/types/domain/usecases/extractNextStepAgentFromComments.d.ts +5 -0
  109. package/types/domain/usecases/extractNextStepAgentFromComments.d.ts.map +1 -0
  110. package/types/domain/usecases/triagerAgentName.d.ts +3 -0
  111. package/types/domain/usecases/triagerAgentName.d.ts.map +1 -0
  112. package/bin/adapter/entry-points/console/ui-dist/assets/index-CaxYBHiT.js +0 -86
  113. package/src/adapter/entry-points/console/ui-dist/assets/index-CaxYBHiT.js +0 -86
  114. package/src/domain/usecases/TriagerApprovalDispatchUseCase.test.ts +0 -1051
  115. package/src/domain/usecases/TriagerApprovalDispatchUseCase.ts +0 -274
  116. package/src/domain/usecases/awaitingOwnerApprovalMessage.ts +0 -5
  117. package/src/domain/usecases/isPullRequestDeclaredUnnecessary.test.ts +0 -162
  118. package/src/domain/usecases/isPullRequestDeclaredUnnecessary.ts +0 -29
  119. package/src/domain/usecases/isWaitingForOwnerApproval.test.ts +0 -91
  120. package/src/domain/usecases/isWaitingForOwnerApproval.ts +0 -17
  121. package/src/domain/usecases/returnedToAwaitingWorkspaceMessage.ts +0 -5
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useState } from 'react';
2
+ import type { AirplaneSnapshot } from '../lib/airplaneSnapshot';
2
3
  import type {
3
4
  ConsoleFieldOption,
4
5
  ConsoleListItem,
@@ -205,6 +206,7 @@ const fetchSnapshots = async (
205
206
 
206
207
  export const useConsoleTabData = (
207
208
  pjcode: string | null,
209
+ airplaneSnapshot: AirplaneSnapshot | null = null,
208
210
  ): ConsoleTabDataState => {
209
211
  const [snapshots, setSnapshots] =
210
212
  useState<Record<ConsoleTabName, ConsoleTabSnapshot | null>>(emptySnapshots);
@@ -212,6 +214,23 @@ export const useConsoleTabData = (
212
214
  const [error, setError] = useState<string | null>(null);
213
215
 
214
216
  useEffect(() => {
217
+ if (airplaneSnapshot !== null && pjcode !== null) {
218
+ const pjTabs = airplaneSnapshot.tabs[pjcode];
219
+ if (pjTabs !== undefined) {
220
+ const next = emptySnapshots();
221
+ for (const tab of CONSOLE_TABS) {
222
+ const stored = pjTabs[tab.name];
223
+ if (stored !== undefined) {
224
+ next[tab.name] = stored;
225
+ }
226
+ }
227
+ setSnapshots(next);
228
+ setIsLoading(false);
229
+ setError(null);
230
+ return;
231
+ }
232
+ }
233
+
215
234
  let cancelled = false;
216
235
  setIsLoading(true);
217
236
  setError(null);
@@ -251,7 +270,7 @@ export const useConsoleTabData = (
251
270
  cancelled = true;
252
271
  clearInterval(timer);
253
272
  };
254
- }, [pjcode]);
273
+ }, [pjcode, airplaneSnapshot]);
255
274
 
256
275
  return { snapshots, isLoading, error };
257
276
  };
@@ -0,0 +1,361 @@
1
+ import {
2
+ parseAirplaneSnapshot,
3
+ readAirplaneModeFlag,
4
+ storeAirplaneSnapshot,
5
+ writeAirplaneModeFlag,
6
+ } from './airplaneSnapshot';
7
+
8
+ const minimalSnapshot = () => ({
9
+ capturedAt: '2026-01-01T00:00:00Z',
10
+ tabs: {},
11
+ items: {},
12
+ failures: [],
13
+ });
14
+
15
+ describe('airplaneSnapshot', () => {
16
+ describe('parseAirplaneSnapshot', () => {
17
+ it('returns null for non-object input', () => {
18
+ expect(parseAirplaneSnapshot(null)).toBeNull();
19
+ expect(parseAirplaneSnapshot('string')).toBeNull();
20
+ expect(parseAirplaneSnapshot(42)).toBeNull();
21
+ });
22
+
23
+ it('parses a minimal valid snapshot', () => {
24
+ const raw = {
25
+ capturedAt: '2026-01-01T00:00:00Z',
26
+ tabs: {},
27
+ items: {},
28
+ failures: [],
29
+ };
30
+ const result = parseAirplaneSnapshot(raw);
31
+ expect(result).not.toBeNull();
32
+ expect(result?.capturedAt).toBe('2026-01-01T00:00:00Z');
33
+ expect(result?.failures).toEqual([]);
34
+ });
35
+
36
+ it('parses tab snapshots with items', () => {
37
+ const raw = {
38
+ capturedAt: '2026-01-01T00:00:00Z',
39
+ tabs: {
40
+ acme: {
41
+ prs: {
42
+ generatedAt: '2026-01-01T00:00:00Z',
43
+ statusOptions: [{ id: 'x', name: 'Open', color: 'GREEN' }],
44
+ storyOptions: [],
45
+ storyColors: {},
46
+ items: [
47
+ {
48
+ number: 1,
49
+ title: 'Fix bug',
50
+ url: 'https://github.com/o/r/pull/1',
51
+ repo: 'o/r',
52
+ nameWithOwner: 'o/r',
53
+ projectItemId: 'PVTI_1',
54
+ itemId: 'PVTI_1',
55
+ isPr: true,
56
+ story: 'regular',
57
+ status: 'In Progress',
58
+ nextActionDate: null,
59
+ nextActionHour: null,
60
+ dependedIssueUrls: [],
61
+ labels: ['bug'],
62
+ createdAt: '2026-01-01T00:00:00Z',
63
+ relatedOpenPullRequestUrls: [],
64
+ },
65
+ ],
66
+ },
67
+ },
68
+ },
69
+ items: {},
70
+ failures: [],
71
+ };
72
+
73
+ const result = parseAirplaneSnapshot(raw);
74
+ expect(result?.tabs.acme?.prs.items).toHaveLength(1);
75
+ expect(result?.tabs.acme?.prs.items[0].title).toBe('Fix bug');
76
+ expect(result?.tabs.acme?.prs.statusOptions).toHaveLength(1);
77
+ expect(result?.tabs.acme?.prs.fromCache).toBe(false);
78
+ expect(result?.tabs.acme?.prs.storyOrder).toEqual([]);
79
+ });
80
+
81
+ it('parses storyOrder and fromCache from a tab snapshot', () => {
82
+ const raw = {
83
+ capturedAt: '2026-01-01T00:00:00Z',
84
+ tabs: {
85
+ acme: {
86
+ prs: {
87
+ generatedAt: '2026-01-01T00:00:00Z',
88
+ statusOptions: [],
89
+ storyOptions: [],
90
+ storyColors: {},
91
+ items: [],
92
+ stories: [],
93
+ defaultNameWithOwner: null,
94
+ storyOrder: ['regular', 'chore'],
95
+ },
96
+ },
97
+ },
98
+ items: {},
99
+ failures: [],
100
+ };
101
+ const result = parseAirplaneSnapshot(raw);
102
+ expect(result?.tabs.acme?.prs.storyOrder).toEqual(['regular', 'chore']);
103
+ expect(result?.tabs.acme?.prs.fromCache).toBe(false);
104
+ });
105
+
106
+ it('parses item snapshots with PR fields', () => {
107
+ const url = 'https://github.com/o/r/pull/1';
108
+ const raw = {
109
+ capturedAt: '2026-01-01T00:00:00Z',
110
+ tabs: {},
111
+ failures: [],
112
+ items: {
113
+ [url]: {
114
+ body: 'PR body text',
115
+ comments: [
116
+ {
117
+ author: 'alice',
118
+ body: 'LGTM',
119
+ createdAt: '2026-01-01T01:00:00Z',
120
+ },
121
+ ],
122
+ state: {
123
+ state: 'open',
124
+ merged: false,
125
+ isPullRequest: true,
126
+ title: 'PR',
127
+ },
128
+ files: [
129
+ {
130
+ path: 'src/foo.ts',
131
+ additions: 5,
132
+ deletions: 2,
133
+ status: 'modified',
134
+ patch: '@@ diff @@',
135
+ rawUrl: null,
136
+ },
137
+ ],
138
+ commits: [
139
+ {
140
+ sha: 'abc',
141
+ message: 'fix',
142
+ author: 'alice',
143
+ authoredAt: '2026-01-01T00:00:00Z',
144
+ },
145
+ ],
146
+ prStatus: {
147
+ found: true,
148
+ isConflicted: false,
149
+ mergeableStatus: 'MERGEABLE',
150
+ isPassedAllCiJob: true,
151
+ isCiStateSuccess: true,
152
+ isBranchOutOfDate: false,
153
+ missingRequiredCheckNames: [],
154
+ },
155
+ relatedPrs: null,
156
+ },
157
+ },
158
+ };
159
+
160
+ const result = parseAirplaneSnapshot(raw);
161
+ const item = result?.items[url];
162
+ expect(item?.body).toBe('PR body text');
163
+ expect(item?.comments).toHaveLength(1);
164
+ expect(item?.comments[0].author).toBe('alice');
165
+ expect(item?.state.isPullRequest).toBe(true);
166
+ expect(item?.files).toHaveLength(1);
167
+ expect(item?.files?.[0].path).toBe('src/foo.ts');
168
+ expect(item?.commits).toHaveLength(1);
169
+ expect(item?.prStatus?.isPassedAllCiJob).toBe(true);
170
+ expect(item?.prStatus?.mergeableStatus).toBe('MERGEABLE');
171
+ expect(item?.relatedPrs).toBeNull();
172
+ });
173
+
174
+ it('parses item snapshots with issue fields', () => {
175
+ const url = 'https://github.com/o/r/issues/5';
176
+ const raw = {
177
+ capturedAt: '2026-01-01T00:00:00Z',
178
+ tabs: {},
179
+ failures: [],
180
+ items: {
181
+ [url]: {
182
+ body: 'Issue body',
183
+ comments: [],
184
+ state: {
185
+ state: 'open',
186
+ merged: false,
187
+ isPullRequest: false,
188
+ title: 'Issue',
189
+ },
190
+ files: null,
191
+ commits: null,
192
+ prStatus: null,
193
+ relatedPrs: [
194
+ {
195
+ url: 'https://github.com/o/r/pull/10',
196
+ branchName: 'fix-branch',
197
+ createdAt: '2026-01-01T00:00:00Z',
198
+ isDraft: false,
199
+ isConflicted: false,
200
+ mergeableStatus: 'MERGEABLE',
201
+ isPassedAllCiJob: true,
202
+ isCiStateSuccess: true,
203
+ isResolvedAllReviewComments: true,
204
+ isBranchOutOfDate: false,
205
+ missingRequiredCheckNames: [],
206
+ summary: null,
207
+ },
208
+ ],
209
+ },
210
+ },
211
+ };
212
+
213
+ const result = parseAirplaneSnapshot(raw);
214
+ const item = result?.items[url];
215
+ expect(item?.files).toBeNull();
216
+ expect(item?.commits).toBeNull();
217
+ expect(item?.prStatus).toBeNull();
218
+ expect(item?.relatedPrs).toHaveLength(1);
219
+ expect(item?.relatedPrs?.[0].url).toBe('https://github.com/o/r/pull/10');
220
+ });
221
+
222
+ it('handles unknown mergeableStatus gracefully', () => {
223
+ const url = 'https://github.com/o/r/pull/2';
224
+ const raw = {
225
+ capturedAt: '',
226
+ tabs: {},
227
+ failures: [],
228
+ items: {
229
+ [url]: {
230
+ body: '',
231
+ comments: [],
232
+ state: {
233
+ state: 'open',
234
+ merged: false,
235
+ isPullRequest: true,
236
+ title: '',
237
+ },
238
+ files: [],
239
+ commits: [],
240
+ prStatus: {
241
+ found: true,
242
+ isConflicted: false,
243
+ mergeableStatus: 'UNKNOWN',
244
+ isPassedAllCiJob: false,
245
+ isCiStateSuccess: false,
246
+ isBranchOutOfDate: false,
247
+ missingRequiredCheckNames: [],
248
+ },
249
+ relatedPrs: null,
250
+ },
251
+ },
252
+ };
253
+ const result = parseAirplaneSnapshot(raw);
254
+ expect(result?.items[url]?.prStatus?.mergeableStatus).toBe('UNKNOWN');
255
+ });
256
+
257
+ it('records failures from the raw payload', () => {
258
+ const raw = {
259
+ capturedAt: '',
260
+ tabs: {},
261
+ items: {},
262
+ failures: ['https://github.com/o/r/issues/99'],
263
+ };
264
+ const result = parseAirplaneSnapshot(raw);
265
+ expect(result?.failures).toContain('https://github.com/o/r/issues/99');
266
+ });
267
+
268
+ it('parses stories and defaultNameWithOwner from a tab snapshot', () => {
269
+ const raw = {
270
+ capturedAt: '2026-01-01T00:00:00Z',
271
+ tabs: {
272
+ acme: {
273
+ stories: {
274
+ generatedAt: '2026-01-01T00:00:00Z',
275
+ statusOptions: [],
276
+ storyOptions: [],
277
+ storyColors: {},
278
+ items: [],
279
+ stories: [
280
+ {
281
+ storyName: 'regular',
282
+ storyOptionId: 'opt-1',
283
+ color: 'GREEN',
284
+ openItemCount: 3,
285
+ },
286
+ ],
287
+ defaultNameWithOwner: 'owner/repo',
288
+ },
289
+ },
290
+ },
291
+ items: {},
292
+ failures: [],
293
+ };
294
+ const result = parseAirplaneSnapshot(raw);
295
+ const storiesTab = result?.tabs.acme?.stories;
296
+ expect(storiesTab?.stories).toHaveLength(1);
297
+ expect(storiesTab?.stories[0].storyName).toBe('regular');
298
+ expect(storiesTab?.defaultNameWithOwner).toBe('owner/repo');
299
+ });
300
+ });
301
+
302
+ describe('readAirplaneModeFlag / writeAirplaneModeFlag', () => {
303
+ beforeEach(() => {
304
+ localStorage.clear();
305
+ });
306
+
307
+ it('returns false when the flag has never been written', () => {
308
+ expect(readAirplaneModeFlag()).toBe(false);
309
+ });
310
+
311
+ it('returns true after writeAirplaneModeFlag(true)', () => {
312
+ writeAirplaneModeFlag(true);
313
+ expect(readAirplaneModeFlag()).toBe(true);
314
+ });
315
+
316
+ it('returns false after writeAirplaneModeFlag(false)', () => {
317
+ writeAirplaneModeFlag(true);
318
+ writeAirplaneModeFlag(false);
319
+ expect(readAirplaneModeFlag()).toBe(false);
320
+ });
321
+ });
322
+
323
+ describe('storeAirplaneSnapshot', () => {
324
+ it('rejects when Cache API is unavailable', async () => {
325
+ const originalCaches = globalThis.caches;
326
+ Object.defineProperty(globalThis, 'caches', {
327
+ configurable: true,
328
+ writable: true,
329
+ value: undefined,
330
+ });
331
+ await expect(storeAirplaneSnapshot(minimalSnapshot())).rejects.toThrow(
332
+ 'Cache API unavailable',
333
+ );
334
+ Object.defineProperty(globalThis, 'caches', {
335
+ configurable: true,
336
+ writable: true,
337
+ value: originalCaches,
338
+ });
339
+ });
340
+
341
+ it('rejects with a user-readable message when cache.put throws QuotaExceededError', async () => {
342
+ const quotaError = new DOMException(
343
+ 'QuotaExceededError',
344
+ 'QuotaExceededError',
345
+ );
346
+ const mockCache = {
347
+ put: jest.fn().mockRejectedValue(quotaError),
348
+ };
349
+ Object.defineProperty(globalThis, 'caches', {
350
+ configurable: true,
351
+ writable: true,
352
+ value: {
353
+ open: jest.fn().mockResolvedValue(mockCache),
354
+ },
355
+ });
356
+ await expect(storeAirplaneSnapshot(minimalSnapshot())).rejects.toThrow(
357
+ 'browser storage',
358
+ );
359
+ });
360
+ });
361
+ });