github-issue-tower-defence-management 1.145.0 → 1.146.1
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/CHANGELOG.md +22 -8
- package/README.md +1 -1
- package/bin/adapter/entry-points/cli/index.js +9 -7
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/console/ensureConsoleRunning.js +6 -1
- package/bin/adapter/entry-points/console/ensureConsoleRunning.js.map +1 -1
- package/bin/adapter/entry-points/console/webServer.js +1 -3
- package/bin/adapter/entry-points/console/webServer.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +3 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/repositories/GraphqlProjectRepository.js +75 -4
- package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
- package/bin/adapter/repositories/utils.js +2 -4
- package/bin/adapter/repositories/utils.js.map +1 -1
- package/bin/domain/entities/ProjectFieldName.js +6 -0
- package/bin/domain/entities/ProjectFieldName.js.map +1 -0
- package/bin/domain/entities/RequiredProjectField.js +47 -0
- package/bin/domain/entities/RequiredProjectField.js.map +1 -0
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +5 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js +25 -0
- package/bin/domain/usecases/ProjectRequiredFieldCreateUseCase.js.map +1 -0
- package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js +6 -1
- package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js.map +1 -1
- package/bin/domain/usecases/dashboard/DashboardProjectCode.js +17 -12
- package/bin/domain/usecases/dashboard/DashboardProjectCode.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +62 -2
- package/src/adapter/entry-points/cli/index.ts +12 -11
- package/src/adapter/entry-points/console/consoleDataDelivery.test.ts +37 -39
- package/src/adapter/entry-points/console/consoleDoneStore.test.ts +33 -33
- package/src/adapter/entry-points/console/consoleItemUrlLookup.test.ts +6 -6
- package/src/adapter/entry-points/console/consoleOperationApi.test.ts +66 -66
- package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +35 -35
- package/src/adapter/entry-points/console/dashboardComposeService.test.ts +28 -28
- package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +26 -18
- package/src/adapter/entry-points/console/ensureConsoleRunning.test.ts +19 -2
- package/src/adapter/entry-points/console/ensureConsoleRunning.ts +8 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +1 -1
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +1 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +2 -2
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +3 -3
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +19 -31
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +24 -32
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOverlay.test.ts +6 -6
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsolePjcode.test.ts +4 -4
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +8 -8
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +5 -5
- package/src/adapter/entry-points/console/ui/src/features/console/logic/overlay.test.ts +1 -1
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +13 -13
- package/src/adapter/entry-points/console/webServer.test.ts +55 -52
- package/src/adapter/entry-points/console/webServer.ts +0 -3
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +2 -2
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +4 -0
- package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +5 -5
- package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +5 -5
- package/src/adapter/repositories/GraphqlProjectRepository.ts +110 -4
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +1 -1
- package/src/adapter/repositories/utils.ts +3 -3
- package/src/domain/entities/ProjectFieldName.ts +2 -0
- package/src/domain/entities/RequiredProjectField.ts +64 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +4 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +5 -0
- package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +3 -3
- package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +159 -0
- package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.ts +32 -0
- package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.test.ts +19 -19
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +24 -14
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +10 -1
- package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +6 -0
- package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +28 -28
- package/src/domain/usecases/dashboard/DashboardProjectCode.test.ts +21 -19
- package/src/domain/usecases/dashboard/DashboardProjectCode.ts +21 -14
- package/types/adapter/entry-points/console/ensureConsoleRunning.d.ts +1 -1
- package/types/adapter/entry-points/console/ensureConsoleRunning.d.ts.map +1 -1
- package/types/adapter/entry-points/console/webServer.d.ts +0 -1
- package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/repositories/GraphqlProjectRepository.d.ts +4 -1
- package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
- package/types/adapter/repositories/utils.d.ts.map +1 -1
- package/types/domain/entities/ProjectFieldName.d.ts +2 -0
- package/types/domain/entities/ProjectFieldName.d.ts.map +1 -0
- package/types/domain/entities/RequiredProjectField.d.ts +13 -0
- package/types/domain/entities/RequiredProjectField.d.ts.map +1 -0
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +3 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts +10 -0
- package/types/domain/usecases/ProjectRequiredFieldCreateUseCase.d.ts.map +1 -0
- package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts +3 -0
- package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts.map +1 -1
- package/types/domain/usecases/dashboard/DashboardProjectCode.d.ts +2 -2
- package/types/domain/usecases/dashboard/DashboardProjectCode.d.ts.map +1 -1
|
@@ -36,11 +36,8 @@ import { GitHubIssueCommentRepository } from '../../repositories/GitHubIssueComm
|
|
|
36
36
|
import { FetchWebhookRepository } from '../../repositories/FetchWebhookRepository';
|
|
37
37
|
import { RevertOrphanedPreparationUseCase } from '../../../domain/usecases/RevertOrphanedPreparationUseCase';
|
|
38
38
|
import * as path from 'path';
|
|
39
|
-
import {
|
|
40
|
-
|
|
41
|
-
DEFAULT_WEB_PORT,
|
|
42
|
-
startWebServer,
|
|
43
|
-
} from '../console/webServer';
|
|
39
|
+
import { DEFAULT_WEB_PORT, startWebServer } from '../console/webServer';
|
|
40
|
+
import { assertDashboardDisplayLabelsUnique } from '../../../domain/usecases/dashboard/DashboardProjectCode';
|
|
44
41
|
import { ensureConsoleRunning } from '../console/ensureConsoleRunning';
|
|
45
42
|
import {
|
|
46
43
|
IssueTitleStateCache,
|
|
@@ -101,14 +98,17 @@ const DEFAULT_DASHBOARD_DIR = '/home/hiromi/0_workspaces/workspace1/jsonpub';
|
|
|
101
98
|
const DEFAULT_DASHBOARD_DATA_DIR: string | null = null;
|
|
102
99
|
|
|
103
100
|
const parseDashboardProjectNames = (raw: string | undefined): string[] => {
|
|
104
|
-
|
|
105
|
-
return DEFAULT_DASHBOARD_PROJECT_NAMES;
|
|
106
|
-
}
|
|
107
|
-
const names = raw
|
|
101
|
+
const names = (raw ?? '')
|
|
108
102
|
.split(',')
|
|
109
103
|
.map((name) => name.trim())
|
|
110
104
|
.filter((name) => name.length > 0);
|
|
111
|
-
|
|
105
|
+
if (names.length === 0) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
'--dashboardProjectNames must list at least one project name',
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
assertDashboardDisplayLabelsUnique(names);
|
|
111
|
+
return names;
|
|
112
112
|
};
|
|
113
113
|
|
|
114
114
|
type SelectOauthTokenOptions = {
|
|
@@ -403,6 +403,7 @@ program
|
|
|
403
403
|
const consoleProcess = await ensureConsoleRunning(
|
|
404
404
|
options.configFilePath,
|
|
405
405
|
DEFAULT_WEB_PORT,
|
|
406
|
+
Object.keys(config.consoleProjects ?? {}),
|
|
406
407
|
);
|
|
407
408
|
if (consoleProcess !== null) {
|
|
408
409
|
process.once('SIGTERM', () => {
|
|
@@ -847,7 +848,7 @@ const addServeWebOptions = (command: Command): Command =>
|
|
|
847
848
|
)
|
|
848
849
|
.option(
|
|
849
850
|
'--dashboardProjectNames <names>',
|
|
850
|
-
|
|
851
|
+
'Comma-separated project names, in display order, for the dashboard project grid; the display label of each project is its first 2 characters, which must be unique across the listed names',
|
|
851
852
|
);
|
|
852
853
|
|
|
853
854
|
addServeWebOptions(program.command('serveWeb'))
|
|
@@ -9,31 +9,31 @@ import { recordDoneProjectItemId } from './consoleDoneStore';
|
|
|
9
9
|
|
|
10
10
|
describe('parseConsoleDataRoute', () => {
|
|
11
11
|
it('parses a list route', () => {
|
|
12
|
-
expect(parseConsoleDataRoute('/projects/
|
|
12
|
+
expect(parseConsoleDataRoute('/projects/acme/prs/list.json')).toEqual({
|
|
13
13
|
kind: 'list',
|
|
14
|
-
pjcode: '
|
|
14
|
+
pjcode: 'acme',
|
|
15
15
|
tab: 'prs',
|
|
16
16
|
});
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
it('parses a list route for todo-by-human', () => {
|
|
20
20
|
expect(
|
|
21
|
-
parseConsoleDataRoute('/projects/
|
|
22
|
-
).toEqual({ kind: 'list', pjcode: '
|
|
21
|
+
parseConsoleDataRoute('/projects/acme/todo-by-human/list.json'),
|
|
22
|
+
).toEqual({ kind: 'list', pjcode: 'acme', tab: 'todo-by-human' });
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
it('parses a list route for todo-by-agent', () => {
|
|
26
26
|
expect(
|
|
27
|
-
parseConsoleDataRoute('/projects/
|
|
28
|
-
).toEqual({ kind: 'list', pjcode: '
|
|
27
|
+
parseConsoleDataRoute('/projects/acme/todo-by-agent/list.json'),
|
|
28
|
+
).toEqual({ kind: 'list', pjcode: 'acme', tab: 'todo-by-agent' });
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
it('parses a detail route', () => {
|
|
32
32
|
expect(
|
|
33
|
-
parseConsoleDataRoute('/projects/
|
|
33
|
+
parseConsoleDataRoute('/projects/acme/triage/detail/123.json'),
|
|
34
34
|
).toEqual({
|
|
35
35
|
kind: 'detail',
|
|
36
|
-
pjcode: '
|
|
36
|
+
pjcode: 'acme',
|
|
37
37
|
tab: 'triage',
|
|
38
38
|
key: '123.json',
|
|
39
39
|
});
|
|
@@ -41,49 +41,47 @@ describe('parseConsoleDataRoute', () => {
|
|
|
41
41
|
|
|
42
42
|
it('parses an in-tmux route', () => {
|
|
43
43
|
expect(
|
|
44
|
-
parseConsoleDataRoute('/projects/
|
|
44
|
+
parseConsoleDataRoute('/projects/acme/in-tmux-by-human/list.json'),
|
|
45
45
|
).toEqual({
|
|
46
46
|
kind: 'in-tmux',
|
|
47
|
-
pjcode: '
|
|
47
|
+
pjcode: 'acme',
|
|
48
48
|
relativePath: 'list.json',
|
|
49
49
|
});
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
it('parses a nested in-tmux route', () => {
|
|
53
53
|
expect(
|
|
54
|
-
parseConsoleDataRoute('/projects/
|
|
54
|
+
parseConsoleDataRoute('/projects/acme/in-tmux-by-human/sub/data.json'),
|
|
55
55
|
).toEqual({
|
|
56
56
|
kind: 'in-tmux',
|
|
57
|
-
pjcode: '
|
|
57
|
+
pjcode: 'acme',
|
|
58
58
|
relativePath: 'sub/data.json',
|
|
59
59
|
});
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
it('rejects unknown tabs', () => {
|
|
63
63
|
expect(
|
|
64
|
-
parseConsoleDataRoute('/projects/
|
|
64
|
+
parseConsoleDataRoute('/projects/acme/unknown/list.json'),
|
|
65
65
|
).toBeNull();
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
it('rejects a non-projects prefix', () => {
|
|
69
|
-
expect(parseConsoleDataRoute('/other/
|
|
69
|
+
expect(parseConsoleDataRoute('/other/acme/prs/list.json')).toBeNull();
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
it('rejects dot segments in pjcode or tab', () => {
|
|
73
73
|
expect(parseConsoleDataRoute('/projects/../prs/list.json')).toBeNull();
|
|
74
|
-
expect(parseConsoleDataRoute('/projects/
|
|
74
|
+
expect(parseConsoleDataRoute('/projects/acme/../list.json')).toBeNull();
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
it('rejects a non-json detail key', () => {
|
|
78
78
|
expect(
|
|
79
|
-
parseConsoleDataRoute('/projects/
|
|
79
|
+
parseConsoleDataRoute('/projects/acme/prs/detail/123.txt'),
|
|
80
80
|
).toBeNull();
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
it('rejects an empty in-tmux relative path', () => {
|
|
84
|
-
expect(
|
|
85
|
-
parseConsoleDataRoute('/projects/umino/in-tmux-by-human'),
|
|
86
|
-
).toBeNull();
|
|
84
|
+
expect(parseConsoleDataRoute('/projects/acme/in-tmux-by-human')).toBeNull();
|
|
87
85
|
});
|
|
88
86
|
});
|
|
89
87
|
|
|
@@ -107,72 +105,72 @@ describe('buildConsoleDataResponse', () => {
|
|
|
107
105
|
it('returns 404 when the list file is absent', () => {
|
|
108
106
|
const response = buildConsoleDataResponse(baseDir, {
|
|
109
107
|
kind: 'list',
|
|
110
|
-
pjcode: '
|
|
108
|
+
pjcode: 'acme',
|
|
111
109
|
tab: 'prs',
|
|
112
110
|
});
|
|
113
111
|
expect(response.statusCode).toBe(404);
|
|
114
112
|
});
|
|
115
113
|
|
|
116
114
|
it('serves a list file and applies the done exclusion', () => {
|
|
117
|
-
writeJson('
|
|
118
|
-
pjcode: '
|
|
115
|
+
writeJson('acme/prs/list.json', {
|
|
116
|
+
pjcode: 'acme',
|
|
119
117
|
items: [
|
|
120
118
|
{ projectItemId: 'PVTI_1', title: 'keep' },
|
|
121
119
|
{ projectItemId: 'PVTI_2', title: 'drop' },
|
|
122
120
|
],
|
|
123
121
|
});
|
|
124
|
-
recordDoneProjectItemId(baseDir, '
|
|
122
|
+
recordDoneProjectItemId(baseDir, 'acme', 'prs', 'PVTI_2');
|
|
125
123
|
const response = buildConsoleDataResponse(baseDir, {
|
|
126
124
|
kind: 'list',
|
|
127
|
-
pjcode: '
|
|
125
|
+
pjcode: 'acme',
|
|
128
126
|
tab: 'prs',
|
|
129
127
|
});
|
|
130
128
|
expect(response.statusCode).toBe(200);
|
|
131
129
|
const parsed: unknown = JSON.parse(response.body);
|
|
132
130
|
expect(parsed).toEqual({
|
|
133
|
-
pjcode: '
|
|
131
|
+
pjcode: 'acme',
|
|
134
132
|
items: [{ projectItemId: 'PVTI_1', title: 'keep' }],
|
|
135
133
|
});
|
|
136
134
|
});
|
|
137
135
|
|
|
138
136
|
it('applies the done exclusion to the workflow-blocker list', () => {
|
|
139
|
-
writeJson('
|
|
140
|
-
pjcode: '
|
|
137
|
+
writeJson('acme/workflow-blocker/list.json', {
|
|
138
|
+
pjcode: 'acme',
|
|
141
139
|
items: [
|
|
142
140
|
{ projectItemId: 'PVTI_1', title: 'keep' },
|
|
143
141
|
{ projectItemId: 'PVTI_2', title: 'processed blocker' },
|
|
144
142
|
],
|
|
145
143
|
});
|
|
146
|
-
recordDoneProjectItemId(baseDir, '
|
|
144
|
+
recordDoneProjectItemId(baseDir, 'acme', 'workflow-blocker', 'PVTI_2');
|
|
147
145
|
const response = buildConsoleDataResponse(baseDir, {
|
|
148
146
|
kind: 'list',
|
|
149
|
-
pjcode: '
|
|
147
|
+
pjcode: 'acme',
|
|
150
148
|
tab: 'workflow-blocker',
|
|
151
149
|
});
|
|
152
150
|
expect(response.statusCode).toBe(200);
|
|
153
151
|
const parsed: unknown = JSON.parse(response.body);
|
|
154
152
|
expect(parsed).toEqual({
|
|
155
|
-
pjcode: '
|
|
153
|
+
pjcode: 'acme',
|
|
156
154
|
items: [{ projectItemId: 'PVTI_1', title: 'keep' }],
|
|
157
155
|
});
|
|
158
156
|
});
|
|
159
157
|
|
|
160
158
|
it('serves a list file without an items array unchanged', () => {
|
|
161
|
-
writeJson('
|
|
159
|
+
writeJson('acme/prs/list.json', { pjcode: 'acme' });
|
|
162
160
|
const response = buildConsoleDataResponse(baseDir, {
|
|
163
161
|
kind: 'list',
|
|
164
|
-
pjcode: '
|
|
162
|
+
pjcode: 'acme',
|
|
165
163
|
tab: 'prs',
|
|
166
164
|
});
|
|
167
165
|
expect(response.statusCode).toBe(200);
|
|
168
|
-
expect(JSON.parse(response.body)).toEqual({ pjcode: '
|
|
166
|
+
expect(JSON.parse(response.body)).toEqual({ pjcode: 'acme' });
|
|
169
167
|
});
|
|
170
168
|
|
|
171
169
|
it('serves a detail file without exclusion', () => {
|
|
172
|
-
writeJson('
|
|
170
|
+
writeJson('acme/triage/detail/123.json', { number: 123 });
|
|
173
171
|
const response = buildConsoleDataResponse(baseDir, {
|
|
174
172
|
kind: 'detail',
|
|
175
|
-
pjcode: '
|
|
173
|
+
pjcode: 'acme',
|
|
176
174
|
tab: 'triage',
|
|
177
175
|
key: '123.json',
|
|
178
176
|
});
|
|
@@ -183,7 +181,7 @@ describe('buildConsoleDataResponse', () => {
|
|
|
183
181
|
it('returns 404 when the detail file is absent', () => {
|
|
184
182
|
const response = buildConsoleDataResponse(baseDir, {
|
|
185
183
|
kind: 'detail',
|
|
186
|
-
pjcode: '
|
|
184
|
+
pjcode: 'acme',
|
|
187
185
|
tab: 'triage',
|
|
188
186
|
key: '404.json',
|
|
189
187
|
});
|
|
@@ -191,10 +189,10 @@ describe('buildConsoleDataResponse', () => {
|
|
|
191
189
|
});
|
|
192
190
|
|
|
193
191
|
it('serves an in-tmux file', () => {
|
|
194
|
-
writeJson('
|
|
192
|
+
writeJson('acme/in-tmux-by-human/list.json', { items: [] });
|
|
195
193
|
const response = buildConsoleDataResponse(baseDir, {
|
|
196
194
|
kind: 'in-tmux',
|
|
197
|
-
pjcode: '
|
|
195
|
+
pjcode: 'acme',
|
|
198
196
|
relativePath: 'list.json',
|
|
199
197
|
});
|
|
200
198
|
expect(response.statusCode).toBe(200);
|
|
@@ -204,7 +202,7 @@ describe('buildConsoleDataResponse', () => {
|
|
|
204
202
|
it('returns 404 when the in-tmux file is absent', () => {
|
|
205
203
|
const response = buildConsoleDataResponse(baseDir, {
|
|
206
204
|
kind: 'in-tmux',
|
|
207
|
-
pjcode: '
|
|
205
|
+
pjcode: 'acme',
|
|
208
206
|
relativePath: 'missing.json',
|
|
209
207
|
});
|
|
210
208
|
expect(response.statusCode).toBe(404);
|
|
@@ -25,48 +25,48 @@ describe('consoleDoneStore', () => {
|
|
|
25
25
|
|
|
26
26
|
describe('doneFilePathForTab', () => {
|
|
27
27
|
it('places the done file under pjcode/tab', () => {
|
|
28
|
-
expect(doneFilePathForTab(baseDir, '
|
|
29
|
-
path.join(baseDir, '
|
|
28
|
+
expect(doneFilePathForTab(baseDir, 'acme', 'prs')).toBe(
|
|
29
|
+
path.join(baseDir, 'acme', 'prs', CONSOLE_DONE_FILE_NAME),
|
|
30
30
|
);
|
|
31
31
|
});
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
describe('readDoneProjectItemIds', () => {
|
|
35
35
|
it('returns an empty array when the file is absent', () => {
|
|
36
|
-
expect(readDoneProjectItemIds(baseDir, '
|
|
36
|
+
expect(readDoneProjectItemIds(baseDir, 'acme', 'prs')).toEqual([]);
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
it('returns the persisted project item ids', () => {
|
|
40
|
-
recordDoneProjectItemId(baseDir, '
|
|
41
|
-
recordDoneProjectItemId(baseDir, '
|
|
42
|
-
expect(readDoneProjectItemIds(baseDir, '
|
|
40
|
+
recordDoneProjectItemId(baseDir, 'acme', 'prs', 'PVTI_1');
|
|
41
|
+
recordDoneProjectItemId(baseDir, 'acme', 'prs', 'PVTI_2');
|
|
42
|
+
expect(readDoneProjectItemIds(baseDir, 'acme', 'prs')).toEqual([
|
|
43
43
|
'PVTI_1',
|
|
44
44
|
'PVTI_2',
|
|
45
45
|
]);
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
it('returns an empty array for malformed json', () => {
|
|
49
|
-
const filePath = doneFilePathForTab(baseDir, '
|
|
49
|
+
const filePath = doneFilePathForTab(baseDir, 'acme', 'prs');
|
|
50
50
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
51
51
|
fs.writeFileSync(filePath, '{ not json');
|
|
52
|
-
expect(() => readDoneProjectItemIds(baseDir, '
|
|
52
|
+
expect(() => readDoneProjectItemIds(baseDir, 'acme', 'prs')).toThrow();
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
it('returns an empty array when projectItemIds is not an array', () => {
|
|
56
|
-
const filePath = doneFilePathForTab(baseDir, '
|
|
56
|
+
const filePath = doneFilePathForTab(baseDir, 'acme', 'prs');
|
|
57
57
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
58
58
|
fs.writeFileSync(filePath, JSON.stringify({ projectItemIds: 'x' }));
|
|
59
|
-
expect(readDoneProjectItemIds(baseDir, '
|
|
59
|
+
expect(readDoneProjectItemIds(baseDir, 'acme', 'prs')).toEqual([]);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
it('filters out non-string entries', () => {
|
|
63
|
-
const filePath = doneFilePathForTab(baseDir, '
|
|
63
|
+
const filePath = doneFilePathForTab(baseDir, 'acme', 'prs');
|
|
64
64
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
65
65
|
fs.writeFileSync(
|
|
66
66
|
filePath,
|
|
67
67
|
JSON.stringify({ projectItemIds: ['PVTI_1', 1, null, ''] }),
|
|
68
68
|
);
|
|
69
|
-
expect(readDoneProjectItemIds(baseDir, '
|
|
69
|
+
expect(readDoneProjectItemIds(baseDir, 'acme', 'prs')).toEqual([
|
|
70
70
|
'PVTI_1',
|
|
71
71
|
]);
|
|
72
72
|
});
|
|
@@ -74,14 +74,14 @@ describe('consoleDoneStore', () => {
|
|
|
74
74
|
|
|
75
75
|
describe('recordDoneProjectItemId', () => {
|
|
76
76
|
it('does not record an empty id', () => {
|
|
77
|
-
recordDoneProjectItemId(baseDir, '
|
|
78
|
-
expect(readDoneProjectItemIds(baseDir, '
|
|
77
|
+
recordDoneProjectItemId(baseDir, 'acme', 'prs', '');
|
|
78
|
+
expect(readDoneProjectItemIds(baseDir, 'acme', 'prs')).toEqual([]);
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
it('does not duplicate an already recorded id', () => {
|
|
82
|
-
recordDoneProjectItemId(baseDir, '
|
|
83
|
-
recordDoneProjectItemId(baseDir, '
|
|
84
|
-
expect(readDoneProjectItemIds(baseDir, '
|
|
82
|
+
recordDoneProjectItemId(baseDir, 'acme', 'prs', 'PVTI_1');
|
|
83
|
+
recordDoneProjectItemId(baseDir, 'acme', 'prs', 'PVTI_1');
|
|
84
|
+
expect(readDoneProjectItemIds(baseDir, 'acme', 'prs')).toEqual([
|
|
85
85
|
'PVTI_1',
|
|
86
86
|
]);
|
|
87
87
|
});
|
|
@@ -89,9 +89,9 @@ describe('consoleDoneStore', () => {
|
|
|
89
89
|
|
|
90
90
|
describe('recordDoneProjectItemIdAcrossTabs', () => {
|
|
91
91
|
it('records the id into every tab done file', () => {
|
|
92
|
-
recordDoneProjectItemIdAcrossTabs(baseDir, '
|
|
92
|
+
recordDoneProjectItemIdAcrossTabs(baseDir, 'acme', 'PVTI_99');
|
|
93
93
|
for (const tab of CONSOLE_DONE_TAB_NAMES) {
|
|
94
|
-
expect(readDoneProjectItemIds(baseDir, '
|
|
94
|
+
expect(readDoneProjectItemIds(baseDir, 'acme', tab)).toEqual([
|
|
95
95
|
'PVTI_99',
|
|
96
96
|
]);
|
|
97
97
|
}
|
|
@@ -103,32 +103,32 @@ describe('consoleDoneStore', () => {
|
|
|
103
103
|
|
|
104
104
|
it('includes the workflow-blocker tab so processed blockers are excluded', () => {
|
|
105
105
|
expect(CONSOLE_DONE_TAB_NAMES).toContain('workflow-blocker');
|
|
106
|
-
recordDoneProjectItemIdAcrossTabs(baseDir, '
|
|
106
|
+
recordDoneProjectItemIdAcrossTabs(baseDir, 'acme', 'PVTI_7');
|
|
107
107
|
expect(
|
|
108
|
-
readDoneProjectItemIds(baseDir, '
|
|
108
|
+
readDoneProjectItemIds(baseDir, 'acme', 'workflow-blocker'),
|
|
109
109
|
).toEqual(['PVTI_7']);
|
|
110
110
|
});
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
describe('resetDoneProjectItemIds', () => {
|
|
114
114
|
it('clears an accumulated done file to an empty record', () => {
|
|
115
|
-
recordDoneProjectItemId(baseDir, '
|
|
116
|
-
recordDoneProjectItemId(baseDir, '
|
|
117
|
-
recordDoneProjectItemId(baseDir, '
|
|
118
|
-
resetDoneProjectItemIds(baseDir, '
|
|
119
|
-
expect(readDoneProjectItemIds(baseDir, '
|
|
115
|
+
recordDoneProjectItemId(baseDir, 'acme', 'prs', 'PVTI_1');
|
|
116
|
+
recordDoneProjectItemId(baseDir, 'acme', 'prs', 'PVTI_2');
|
|
117
|
+
recordDoneProjectItemId(baseDir, 'acme', 'prs', 'PVTI_3');
|
|
118
|
+
resetDoneProjectItemIds(baseDir, 'acme', 'prs');
|
|
119
|
+
expect(readDoneProjectItemIds(baseDir, 'acme', 'prs')).toEqual([]);
|
|
120
120
|
});
|
|
121
121
|
|
|
122
122
|
it('writes an empty record when no done file exists yet', () => {
|
|
123
|
-
resetDoneProjectItemIds(baseDir, '
|
|
124
|
-
const filePath = doneFilePathForTab(baseDir, '
|
|
123
|
+
resetDoneProjectItemIds(baseDir, 'acme', 'prs');
|
|
124
|
+
const filePath = doneFilePathForTab(baseDir, 'acme', 'prs');
|
|
125
125
|
const raw: unknown = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
126
126
|
expect(raw).toEqual({ projectItemIds: [] });
|
|
127
127
|
});
|
|
128
128
|
|
|
129
129
|
it('does not leave a temp file behind', () => {
|
|
130
|
-
resetDoneProjectItemIds(baseDir, '
|
|
131
|
-
const filePath = doneFilePathForTab(baseDir, '
|
|
130
|
+
resetDoneProjectItemIds(baseDir, 'acme', 'prs');
|
|
131
|
+
const filePath = doneFilePathForTab(baseDir, 'acme', 'prs');
|
|
132
132
|
expect(fs.existsSync(`${filePath}.tmp`)).toBe(false);
|
|
133
133
|
});
|
|
134
134
|
});
|
|
@@ -137,11 +137,11 @@ describe('consoleDoneStore', () => {
|
|
|
137
137
|
it('clears the done file of every tab including in-tmux-by-human', () => {
|
|
138
138
|
expect(CONSOLE_DONE_TAB_NAMES).toContain('in-tmux-by-human');
|
|
139
139
|
for (const tab of CONSOLE_DONE_TAB_NAMES) {
|
|
140
|
-
recordDoneProjectItemId(baseDir, '
|
|
140
|
+
recordDoneProjectItemId(baseDir, 'acme', tab, 'PVTI_1');
|
|
141
141
|
}
|
|
142
|
-
resetDoneProjectItemIdsAcrossTabs(baseDir, '
|
|
142
|
+
resetDoneProjectItemIdsAcrossTabs(baseDir, 'acme');
|
|
143
143
|
for (const tab of CONSOLE_DONE_TAB_NAMES) {
|
|
144
|
-
expect(readDoneProjectItemIds(baseDir, '
|
|
144
|
+
expect(readDoneProjectItemIds(baseDir, 'acme', tab)).toEqual([]);
|
|
145
145
|
}
|
|
146
146
|
});
|
|
147
147
|
});
|
|
@@ -15,7 +15,7 @@ describe('findConsoleItemUrl', () => {
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
const writeList = (tab: string, items: unknown): void => {
|
|
18
|
-
const dir = path.join(baseDir, '
|
|
18
|
+
const dir = path.join(baseDir, 'acme', tab);
|
|
19
19
|
fs.mkdirSync(dir, { recursive: true });
|
|
20
20
|
fs.writeFileSync(path.join(dir, 'list.json'), JSON.stringify({ items }));
|
|
21
21
|
};
|
|
@@ -23,14 +23,14 @@ describe('findConsoleItemUrl', () => {
|
|
|
23
23
|
it('returns the stored url when the requested url is listed in a tab', () => {
|
|
24
24
|
writeList('prs', [{ url: 'https://github.com/o/r/issues/1' }]);
|
|
25
25
|
expect(
|
|
26
|
-
findConsoleItemUrl(baseDir, '
|
|
26
|
+
findConsoleItemUrl(baseDir, 'acme', 'https://github.com/o/r/issues/1'),
|
|
27
27
|
).toBe('https://github.com/o/r/issues/1');
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
it('returns null when no tab lists the requested url', () => {
|
|
31
31
|
writeList('prs', [{ url: 'https://github.com/o/r/issues/1' }]);
|
|
32
32
|
expect(
|
|
33
|
-
findConsoleItemUrl(baseDir, '
|
|
33
|
+
findConsoleItemUrl(baseDir, 'acme', 'https://github.com/o/r/issues/2'),
|
|
34
34
|
).toBeNull();
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -41,12 +41,12 @@ describe('findConsoleItemUrl', () => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
it('ignores a tab file that is not valid json', () => {
|
|
44
|
-
const dir = path.join(baseDir, '
|
|
44
|
+
const dir = path.join(baseDir, 'acme', 'triage');
|
|
45
45
|
fs.mkdirSync(dir, { recursive: true });
|
|
46
46
|
fs.writeFileSync(path.join(dir, 'list.json'), 'not json');
|
|
47
47
|
writeList('prs', [{ url: 'https://github.com/o/r/issues/1' }]);
|
|
48
48
|
expect(
|
|
49
|
-
findConsoleItemUrl(baseDir, '
|
|
49
|
+
findConsoleItemUrl(baseDir, 'acme', 'https://github.com/o/r/issues/1'),
|
|
50
50
|
).toBe('https://github.com/o/r/issues/1');
|
|
51
51
|
});
|
|
52
52
|
|
|
@@ -57,7 +57,7 @@ describe('findConsoleItemUrl', () => {
|
|
|
57
57
|
{ url: 'https://github.com/o/r/pull/9' },
|
|
58
58
|
]);
|
|
59
59
|
expect(
|
|
60
|
-
findConsoleItemUrl(baseDir, '
|
|
60
|
+
findConsoleItemUrl(baseDir, 'acme', 'https://github.com/o/r/pull/9'),
|
|
61
61
|
).toBe('https://github.com/o/r/pull/9');
|
|
62
62
|
});
|
|
63
63
|
});
|