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
|
@@ -9,33 +9,33 @@ import {
|
|
|
9
9
|
describe('buildPjcodeToProjectUrl', () => {
|
|
10
10
|
it('adds the default pjcode entry when it is not already present', () => {
|
|
11
11
|
const mapping = buildPjcodeToProjectUrl(
|
|
12
|
-
'
|
|
13
|
-
'https://github.com/orgs/
|
|
14
|
-
{
|
|
12
|
+
'acme',
|
|
13
|
+
'https://github.com/orgs/acme/projects/1',
|
|
14
|
+
{ globex: 'https://github.com/orgs/globex/projects/2' },
|
|
15
15
|
);
|
|
16
16
|
expect(mapping).toEqual({
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
acme: 'https://github.com/orgs/acme/projects/1',
|
|
18
|
+
globex: 'https://github.com/orgs/globex/projects/2',
|
|
19
19
|
});
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
it('keeps an explicit default pjcode entry from consoleProjects', () => {
|
|
23
23
|
const mapping = buildPjcodeToProjectUrl(
|
|
24
|
-
'
|
|
25
|
-
'https://github.com/orgs/
|
|
26
|
-
{
|
|
24
|
+
'acme',
|
|
25
|
+
'https://github.com/orgs/acme/projects/1',
|
|
26
|
+
{ acme: 'https://github.com/orgs/acme/projects/9' },
|
|
27
27
|
);
|
|
28
|
-
expect(mapping.
|
|
28
|
+
expect(mapping.acme).toBe('https://github.com/orgs/acme/projects/9');
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
it('uses only the default entry when no consoleProjects mapping is configured', () => {
|
|
32
32
|
const mapping = buildPjcodeToProjectUrl(
|
|
33
|
-
'
|
|
34
|
-
'https://github.com/orgs/
|
|
33
|
+
'acme',
|
|
34
|
+
'https://github.com/orgs/acme/projects/1',
|
|
35
35
|
null,
|
|
36
36
|
);
|
|
37
37
|
expect(mapping).toEqual({
|
|
38
|
-
|
|
38
|
+
acme: 'https://github.com/orgs/acme/projects/1',
|
|
39
39
|
});
|
|
40
40
|
});
|
|
41
41
|
});
|
|
@@ -43,44 +43,44 @@ describe('buildPjcodeToProjectUrl', () => {
|
|
|
43
43
|
describe('createPjcodeConfigChecker', () => {
|
|
44
44
|
it('reports true only for a configured pjcode without loading any project', () => {
|
|
45
45
|
const isConfigured = createPjcodeConfigChecker({
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
acme: 'https://github.com/orgs/acme/projects/1',
|
|
47
|
+
globex: 'https://github.com/orgs/globex/projects/2',
|
|
48
48
|
});
|
|
49
|
-
expect(isConfigured('
|
|
50
|
-
expect(isConfigured('
|
|
49
|
+
expect(isConfigured('acme')).toBe(true);
|
|
50
|
+
expect(isConfigured('globex')).toBe(true);
|
|
51
51
|
expect(isConfigured('unknown')).toBe(false);
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
describe('createConsoleProjectResolver', () => {
|
|
56
|
-
const
|
|
57
|
-
const
|
|
56
|
+
const acmeProject: Project = { ...mock<Project>(), id: 'PVT_acme' };
|
|
57
|
+
const globexProject: Project = { ...mock<Project>(), id: 'PVT_globex' };
|
|
58
58
|
|
|
59
59
|
it('resolves a known pjcode to its loaded project', async () => {
|
|
60
60
|
const loadProject = jest.fn(async (url: string) =>
|
|
61
|
-
url.includes('
|
|
61
|
+
url.includes('acme') ? acmeProject : globexProject,
|
|
62
62
|
);
|
|
63
63
|
const resolver = createConsoleProjectResolver(
|
|
64
64
|
{
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
acme: 'https://github.com/orgs/acme/projects/1',
|
|
66
|
+
globex: 'https://github.com/orgs/globex/projects/2',
|
|
67
67
|
},
|
|
68
68
|
loadProject,
|
|
69
69
|
);
|
|
70
|
-
await expect(resolver('
|
|
71
|
-
pjcode: '
|
|
72
|
-
project:
|
|
70
|
+
await expect(resolver('acme')).resolves.toEqual({
|
|
71
|
+
pjcode: 'acme',
|
|
72
|
+
project: acmeProject,
|
|
73
73
|
});
|
|
74
|
-
await expect(resolver('
|
|
75
|
-
pjcode: '
|
|
76
|
-
project:
|
|
74
|
+
await expect(resolver('globex')).resolves.toEqual({
|
|
75
|
+
pjcode: 'globex',
|
|
76
|
+
project: globexProject,
|
|
77
77
|
});
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
it('returns null for a pjcode that has no configured project url', async () => {
|
|
81
|
-
const loadProject = jest.fn(async () =>
|
|
81
|
+
const loadProject = jest.fn(async () => acmeProject);
|
|
82
82
|
const resolver = createConsoleProjectResolver(
|
|
83
|
-
{
|
|
83
|
+
{ acme: 'https://github.com/orgs/acme/projects/1' },
|
|
84
84
|
loadProject,
|
|
85
85
|
);
|
|
86
86
|
await expect(resolver('unknown')).resolves.toBeNull();
|
|
@@ -90,20 +90,20 @@ describe('createConsoleProjectResolver', () => {
|
|
|
90
90
|
it('returns null when the project fails to load', async () => {
|
|
91
91
|
const loadProject = jest.fn(async () => null);
|
|
92
92
|
const resolver = createConsoleProjectResolver(
|
|
93
|
-
{
|
|
93
|
+
{ acme: 'https://github.com/orgs/acme/projects/1' },
|
|
94
94
|
loadProject,
|
|
95
95
|
);
|
|
96
|
-
await expect(resolver('
|
|
96
|
+
await expect(resolver('acme')).resolves.toBeNull();
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
it('loads each project at most once and serves later calls from cache', async () => {
|
|
100
|
-
const loadProject = jest.fn(async () =>
|
|
100
|
+
const loadProject = jest.fn(async () => acmeProject);
|
|
101
101
|
const resolver = createConsoleProjectResolver(
|
|
102
|
-
{
|
|
102
|
+
{ acme: 'https://github.com/orgs/acme/projects/1' },
|
|
103
103
|
loadProject,
|
|
104
104
|
);
|
|
105
|
-
await resolver('
|
|
106
|
-
await resolver('
|
|
105
|
+
await resolver('acme');
|
|
106
|
+
await resolver('acme');
|
|
107
107
|
expect(loadProject).toHaveBeenCalledTimes(1);
|
|
108
108
|
});
|
|
109
109
|
});
|
|
@@ -22,8 +22,8 @@ describe('buildComposeDashboardInput', () => {
|
|
|
22
22
|
it('reads every requested project code in order and yields null for absent files', () => {
|
|
23
23
|
const dataDir = makeDataDir();
|
|
24
24
|
try {
|
|
25
|
-
writeProject(dataDir, '
|
|
26
|
-
pjcode: '
|
|
25
|
+
writeProject(dataDir, 'acme', {
|
|
26
|
+
pjcode: 'acme',
|
|
27
27
|
capturedAt: 'x',
|
|
28
28
|
unread: 3,
|
|
29
29
|
todo: 1,
|
|
@@ -36,11 +36,11 @@ describe('buildComposeDashboardInput', () => {
|
|
|
36
36
|
});
|
|
37
37
|
const input = buildComposeDashboardInput({
|
|
38
38
|
dashboardDataDir: dataDir,
|
|
39
|
-
projectNames: ['
|
|
39
|
+
projectNames: ['acme', 'initech'],
|
|
40
40
|
});
|
|
41
41
|
expect(input.projects).toEqual([
|
|
42
42
|
{
|
|
43
|
-
code: '
|
|
43
|
+
code: 'ac',
|
|
44
44
|
row: {
|
|
45
45
|
unread: 3,
|
|
46
46
|
todo: 1,
|
|
@@ -52,7 +52,7 @@ describe('buildComposeDashboardInput', () => {
|
|
|
52
52
|
blocker: 0,
|
|
53
53
|
},
|
|
54
54
|
},
|
|
55
|
-
{ code: '
|
|
55
|
+
{ code: 'in', row: null },
|
|
56
56
|
]);
|
|
57
57
|
} finally {
|
|
58
58
|
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
@@ -62,10 +62,10 @@ describe('buildComposeDashboardInput', () => {
|
|
|
62
62
|
it('treats a project file with a missing field as an absent row', () => {
|
|
63
63
|
const dataDir = makeDataDir();
|
|
64
64
|
try {
|
|
65
|
-
writeProject(dataDir, '
|
|
65
|
+
writeProject(dataDir, 'acme', { unread: 1, todo: 1 });
|
|
66
66
|
const input = buildComposeDashboardInput({
|
|
67
67
|
dashboardDataDir: dataDir,
|
|
68
|
-
projectNames: ['
|
|
68
|
+
projectNames: ['acme'],
|
|
69
69
|
});
|
|
70
70
|
expect(input.projects[0].row).toBeNull();
|
|
71
71
|
} finally {
|
|
@@ -77,13 +77,10 @@ describe('buildComposeDashboardInput', () => {
|
|
|
77
77
|
const dataDir = makeDataDir();
|
|
78
78
|
try {
|
|
79
79
|
fs.mkdirSync(path.join(dataDir, 'projects'), { recursive: true });
|
|
80
|
-
fs.writeFileSync(
|
|
81
|
-
path.join(dataDir, 'projects', 'umino.json'),
|
|
82
|
-
'not json',
|
|
83
|
-
);
|
|
80
|
+
fs.writeFileSync(path.join(dataDir, 'projects', 'acme.json'), 'not json');
|
|
84
81
|
const input = buildComposeDashboardInput({
|
|
85
82
|
dashboardDataDir: dataDir,
|
|
86
|
-
projectNames: ['
|
|
83
|
+
projectNames: ['acme'],
|
|
87
84
|
});
|
|
88
85
|
expect(input.projects[0].row).toBeNull();
|
|
89
86
|
} finally {
|
|
@@ -365,8 +362,8 @@ describe('composeDashboardText', () => {
|
|
|
365
362
|
it('composes the full byte-identical dashboard text from the data files', () => {
|
|
366
363
|
const dataDir = makeDataDir();
|
|
367
364
|
try {
|
|
368
|
-
writeProject(dataDir, '
|
|
369
|
-
pjcode: '
|
|
365
|
+
writeProject(dataDir, 'acme', {
|
|
366
|
+
pjcode: 'acme',
|
|
370
367
|
capturedAt: 'x',
|
|
371
368
|
unread: 3,
|
|
372
369
|
todo: 1,
|
|
@@ -409,14 +406,14 @@ describe('composeDashboardText', () => {
|
|
|
409
406
|
expect(
|
|
410
407
|
composeDashboardText({
|
|
411
408
|
dashboardDataDir: dataDir,
|
|
412
|
-
projectNames: ['
|
|
409
|
+
projectNames: ['acme', 'initech'],
|
|
413
410
|
}),
|
|
414
411
|
).toBe(
|
|
415
412
|
'<tt>M55% C62% D89% cy14</tt><br>\n' +
|
|
416
413
|
'<tt>LA 16 23 40</tt><br>\n' +
|
|
417
414
|
'<tt>pj unr tdo aqc fal prp aws dep</tt><br>\n' +
|
|
418
|
-
'<tt>🟢
|
|
419
|
-
'<tt>
|
|
415
|
+
'<tt>🟢ac 3 1 2 0 0 4 1</tt><br>\n' +
|
|
416
|
+
'<tt> in -- -- -- -- -- -- --</tt><br>\n' +
|
|
420
417
|
'<tt></tt><br>\n' +
|
|
421
418
|
'<tt>🟢alice 10% 0d01h00 12% 5d00h00 2 1</tt><br>\n',
|
|
422
419
|
);
|
|
@@ -446,7 +443,7 @@ describe('dashboardComposeFilesPresent', () => {
|
|
|
446
443
|
};
|
|
447
444
|
|
|
448
445
|
const minimalProject = {
|
|
449
|
-
pjcode: '
|
|
446
|
+
pjcode: 'acme',
|
|
450
447
|
capturedAt: 'x',
|
|
451
448
|
unread: 0,
|
|
452
449
|
todo: 0,
|
|
@@ -462,12 +459,15 @@ describe('dashboardComposeFilesPresent', () => {
|
|
|
462
459
|
const dataDir = makeDataDir();
|
|
463
460
|
try {
|
|
464
461
|
writeMachineAndToken(dataDir);
|
|
465
|
-
writeProject(dataDir, '
|
|
466
|
-
writeProject(dataDir, '
|
|
462
|
+
writeProject(dataDir, 'acme', minimalProject);
|
|
463
|
+
writeProject(dataDir, 'initech', {
|
|
464
|
+
...minimalProject,
|
|
465
|
+
pjcode: 'initech',
|
|
466
|
+
});
|
|
467
467
|
expect(
|
|
468
468
|
dashboardComposeFilesPresent({
|
|
469
469
|
dashboardDataDir: dataDir,
|
|
470
|
-
projectNames: ['
|
|
470
|
+
projectNames: ['acme', 'initech'],
|
|
471
471
|
}),
|
|
472
472
|
).toBe(true);
|
|
473
473
|
} finally {
|
|
@@ -481,7 +481,7 @@ describe('dashboardComposeFilesPresent', () => {
|
|
|
481
481
|
expect(
|
|
482
482
|
dashboardComposeFilesPresent({
|
|
483
483
|
dashboardDataDir: dataDir,
|
|
484
|
-
projectNames: ['
|
|
484
|
+
projectNames: ['acme'],
|
|
485
485
|
}),
|
|
486
486
|
).toBe(false);
|
|
487
487
|
} finally {
|
|
@@ -493,11 +493,11 @@ describe('dashboardComposeFilesPresent', () => {
|
|
|
493
493
|
const dataDir = makeDataDir();
|
|
494
494
|
try {
|
|
495
495
|
writeMachineAndToken(dataDir);
|
|
496
|
-
writeProject(dataDir, '
|
|
496
|
+
writeProject(dataDir, 'acme', minimalProject);
|
|
497
497
|
expect(
|
|
498
498
|
dashboardComposeFilesPresent({
|
|
499
499
|
dashboardDataDir: dataDir,
|
|
500
|
-
projectNames: ['
|
|
500
|
+
projectNames: ['acme', 'initech'],
|
|
501
501
|
}),
|
|
502
502
|
).toBe(false);
|
|
503
503
|
} finally {
|
|
@@ -512,11 +512,11 @@ describe('dashboardComposeFilesPresent', () => {
|
|
|
512
512
|
path.join(dataDir, 'token-status.json'),
|
|
513
513
|
JSON.stringify({ tokens: [], capturedAt: 'x' }),
|
|
514
514
|
);
|
|
515
|
-
writeProject(dataDir, '
|
|
515
|
+
writeProject(dataDir, 'acme', minimalProject);
|
|
516
516
|
expect(
|
|
517
517
|
dashboardComposeFilesPresent({
|
|
518
518
|
dashboardDataDir: dataDir,
|
|
519
|
-
projectNames: ['
|
|
519
|
+
projectNames: ['acme'],
|
|
520
520
|
}),
|
|
521
521
|
).toBe(false);
|
|
522
522
|
} finally {
|
|
@@ -538,11 +538,11 @@ describe('dashboardComposeFilesPresent', () => {
|
|
|
538
538
|
capturedAt: 'x',
|
|
539
539
|
}),
|
|
540
540
|
);
|
|
541
|
-
writeProject(dataDir, '
|
|
541
|
+
writeProject(dataDir, 'acme', minimalProject);
|
|
542
542
|
expect(
|
|
543
543
|
dashboardComposeFilesPresent({
|
|
544
544
|
dashboardDataDir: dataDir,
|
|
545
|
-
projectNames: ['
|
|
545
|
+
projectNames: ['acme'],
|
|
546
546
|
}),
|
|
547
547
|
).toBe(false);
|
|
548
548
|
} finally {
|
|
@@ -2,19 +2,23 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as os from 'os';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import { Issue } from '../../../domain/entities/Issue';
|
|
5
|
-
import {
|
|
6
|
-
DASHBOARD_PROJECT_NAMES,
|
|
7
|
-
toDashboardDisplayLabel,
|
|
8
|
-
} from '../../../domain/usecases/dashboard/DashboardProjectCode';
|
|
5
|
+
import { toDashboardDisplayLabel } from '../../../domain/usecases/dashboard/DashboardProjectCode';
|
|
9
6
|
import { writeDashboardRow } from '../handlers/dashboardRowWriter';
|
|
10
7
|
import {
|
|
11
8
|
composeDashboardText,
|
|
12
9
|
dashboardComposeFilesPresent,
|
|
13
10
|
} from './dashboardComposeService';
|
|
14
|
-
import { DEFAULT_DASHBOARD_PROJECT_NAMES } from './webServer';
|
|
15
11
|
|
|
16
12
|
const ASSIGNEE = 'HiromiShikata';
|
|
17
13
|
|
|
14
|
+
const CONFIGURED_PROJECT_NAMES = [
|
|
15
|
+
'acme',
|
|
16
|
+
'globex',
|
|
17
|
+
'initech',
|
|
18
|
+
'umbrella',
|
|
19
|
+
'soylent',
|
|
20
|
+
];
|
|
21
|
+
|
|
18
22
|
let issueCounter = 0;
|
|
19
23
|
const makeIssue = (overrides: Partial<Issue>): Issue => {
|
|
20
24
|
issueCounter += 1;
|
|
@@ -59,7 +63,7 @@ describe('dashboard emitter filename matches composer lookup key', () => {
|
|
|
59
63
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
60
64
|
});
|
|
61
65
|
|
|
62
|
-
it.each(
|
|
66
|
+
it.each(CONFIGURED_PROJECT_NAMES)(
|
|
63
67
|
'emits projects/%s.json under the full project name the composer reads',
|
|
64
68
|
(projectName) => {
|
|
65
69
|
writeDashboardRow({
|
|
@@ -80,7 +84,7 @@ describe('dashboard emitter filename matches composer lookup key', () => {
|
|
|
80
84
|
);
|
|
81
85
|
|
|
82
86
|
it('detects compose files present when every project name file exists', () => {
|
|
83
|
-
for (const projectName of
|
|
87
|
+
for (const projectName of CONFIGURED_PROJECT_NAMES) {
|
|
84
88
|
writeDashboardRow({
|
|
85
89
|
dashboardDataDir: dir,
|
|
86
90
|
pjcode: projectName,
|
|
@@ -107,19 +111,20 @@ describe('dashboard emitter filename matches composer lookup key', () => {
|
|
|
107
111
|
expect(
|
|
108
112
|
dashboardComposeFilesPresent({
|
|
109
113
|
dashboardDataDir: dir,
|
|
110
|
-
projectNames:
|
|
114
|
+
projectNames: CONFIGURED_PROJECT_NAMES,
|
|
111
115
|
}),
|
|
112
116
|
).toBe(true);
|
|
113
117
|
});
|
|
114
118
|
|
|
115
119
|
it('composes the full grid with no fallback row and shows the 2-char display labels', () => {
|
|
116
120
|
const issuesByName: Record<string, Issue[]> = {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
acme: [makeIssue({ status: 'Unread' }), makeIssue({ status: 'Unread' })],
|
|
122
|
+
globex: [makeIssue({ status: 'Awaiting Quality Check' })],
|
|
123
|
+
initech: [makeIssue({ status: 'Awaiting Workspace' })],
|
|
124
|
+
umbrella: [makeIssue({ status: 'Todo by human' })],
|
|
125
|
+
soylent: [makeIssue({ status: 'Preparation' })],
|
|
121
126
|
};
|
|
122
|
-
for (const projectName of
|
|
127
|
+
for (const projectName of CONFIGURED_PROJECT_NAMES) {
|
|
123
128
|
writeDashboardRow({
|
|
124
129
|
dashboardDataDir: dir,
|
|
125
130
|
pjcode: projectName,
|
|
@@ -145,7 +150,7 @@ describe('dashboard emitter filename matches composer lookup key', () => {
|
|
|
145
150
|
|
|
146
151
|
const composed = composeDashboardText({
|
|
147
152
|
dashboardDataDir: dir,
|
|
148
|
-
projectNames:
|
|
153
|
+
projectNames: CONFIGURED_PROJECT_NAMES,
|
|
149
154
|
});
|
|
150
155
|
|
|
151
156
|
const wrap = (line: string): string =>
|
|
@@ -156,16 +161,19 @@ describe('dashboard emitter filename matches composer lookup key', () => {
|
|
|
156
161
|
wrap('LA 16 23 40'),
|
|
157
162
|
wrap('pj unr tdo aqc fal prp aws dep'),
|
|
158
163
|
wrap(
|
|
159
|
-
`🟢${toDashboardDisplayLabel('
|
|
164
|
+
`🟢${toDashboardDisplayLabel('acme')} 2 0 0 0 0 0 0`,
|
|
165
|
+
),
|
|
166
|
+
wrap(
|
|
167
|
+
`🟢${toDashboardDisplayLabel('globex')} 0 0 1 0 0 0 0`,
|
|
160
168
|
),
|
|
161
169
|
wrap(
|
|
162
|
-
`🟢${toDashboardDisplayLabel('
|
|
170
|
+
`🟢${toDashboardDisplayLabel('initech')} 0 0 0 0 0 1 0`,
|
|
163
171
|
),
|
|
164
172
|
wrap(
|
|
165
|
-
`🟢${toDashboardDisplayLabel('
|
|
173
|
+
`🟢${toDashboardDisplayLabel('umbrella')} 0 1 0 0 0 0 0`,
|
|
166
174
|
),
|
|
167
175
|
wrap(
|
|
168
|
-
`🟢${toDashboardDisplayLabel('
|
|
176
|
+
`🟢${toDashboardDisplayLabel('soylent')} 0 0 0 0 1 0 0`,
|
|
169
177
|
),
|
|
170
178
|
wrap(''),
|
|
171
179
|
].join('\n') + '\n';
|
|
@@ -54,14 +54,20 @@ describe('ensureConsoleRunning', () => {
|
|
|
54
54
|
|
|
55
55
|
it('should short-circuit and return null when the port already responds', async () => {
|
|
56
56
|
const port = await startProbeServer();
|
|
57
|
-
const result = await ensureConsoleRunning('/path/to/config.yaml', port
|
|
57
|
+
const result = await ensureConsoleRunning('/path/to/config.yaml', port, [
|
|
58
|
+
'acme',
|
|
59
|
+
'globex',
|
|
60
|
+
]);
|
|
58
61
|
expect(spawnCalls).toHaveLength(0);
|
|
59
62
|
expect(result).toBeNull();
|
|
60
63
|
});
|
|
61
64
|
|
|
62
65
|
it('should spawn serveWeb when the port is unresponsive', async () => {
|
|
63
66
|
const unusedPort = await findFreePort();
|
|
64
|
-
await ensureConsoleRunning('/path/to/config.yaml', unusedPort
|
|
67
|
+
await ensureConsoleRunning('/path/to/config.yaml', unusedPort, [
|
|
68
|
+
'acme',
|
|
69
|
+
'globex',
|
|
70
|
+
]);
|
|
65
71
|
|
|
66
72
|
expect(spawnCalls).toHaveLength(1);
|
|
67
73
|
const [program, args, options] = spawnCalls[0];
|
|
@@ -80,16 +86,27 @@ describe('ensureConsoleRunning', () => {
|
|
|
80
86
|
if (!isObject(options)) {
|
|
81
87
|
throw new Error('Expected spawn options to be an object');
|
|
82
88
|
}
|
|
89
|
+
expect(args[6]).toBe('--dashboardProjectNames');
|
|
90
|
+
expect(args[7]).toBe('acme,globex');
|
|
83
91
|
expect(options.detached).toBe(true);
|
|
84
92
|
expect(options.stdio).toBe('ignore');
|
|
85
93
|
expect(unrefMock).toHaveBeenCalledTimes(1);
|
|
86
94
|
});
|
|
87
95
|
|
|
96
|
+
it('should refuse to start the console when no project name is configured', async () => {
|
|
97
|
+
const unusedPort = await findFreePort();
|
|
98
|
+
await expect(
|
|
99
|
+
ensureConsoleRunning('/path/to/config.yaml', unusedPort, []),
|
|
100
|
+
).rejects.toThrow('consoleProjects is empty');
|
|
101
|
+
expect(spawnCalls).toHaveLength(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
88
104
|
it('should return an object with a kill method that delegates to the child process', async () => {
|
|
89
105
|
const unusedPort = await findFreePort();
|
|
90
106
|
const result = await ensureConsoleRunning(
|
|
91
107
|
'/path/to/config.yaml',
|
|
92
108
|
unusedPort,
|
|
109
|
+
['acme', 'globex'],
|
|
93
110
|
);
|
|
94
111
|
expect(result).not.toBeNull();
|
|
95
112
|
result?.kill();
|
|
@@ -32,7 +32,13 @@ const sleep = (ms: number): Promise<void> =>
|
|
|
32
32
|
export const ensureConsoleRunning = async (
|
|
33
33
|
configFilePath: string,
|
|
34
34
|
port: number,
|
|
35
|
+
dashboardProjectNames: string[],
|
|
35
36
|
): Promise<ConsoleProcess | null> => {
|
|
37
|
+
if (dashboardProjectNames.length === 0) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Cannot start the console from ${configFilePath}: consoleProjects is empty, so the dashboard has no project to render`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
36
42
|
if (await isPortResponding(port)) return null;
|
|
37
43
|
const cliEntry = path.resolve(__dirname, '../cli/index.js');
|
|
38
44
|
const child = spawn(
|
|
@@ -44,6 +50,8 @@ export const ensureConsoleRunning = async (
|
|
|
44
50
|
configFilePath,
|
|
45
51
|
'--port',
|
|
46
52
|
String(port),
|
|
53
|
+
'--dashboardProjectNames',
|
|
54
|
+
dashboardProjectNames.join(','),
|
|
47
55
|
],
|
|
48
56
|
{
|
|
49
57
|
detached: true,
|
|
@@ -244,7 +244,7 @@ test('lists a still-open item and keeps its tab visible when the browser overlay
|
|
|
244
244
|
|
|
245
245
|
await page.evaluate(() => {
|
|
246
246
|
localStorage.setItem(
|
|
247
|
-
'
|
|
247
|
+
'pv_overlay_acme',
|
|
248
248
|
JSON.stringify({
|
|
249
249
|
PVTI_lADOABCD1234zgTODO00869: {
|
|
250
250
|
done: true,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from '../../consoleReadApi';
|
|
20
20
|
import { startWebServer } from '../../webServer';
|
|
21
21
|
|
|
22
|
-
export const CONSOLE_E2E_PJCODE = '
|
|
22
|
+
export const CONSOLE_E2E_PJCODE = 'acme';
|
|
23
23
|
export const CONSOLE_E2E_TOKEN = 'console-e2e-fixture-token-3f9c1a';
|
|
24
24
|
|
|
25
25
|
export type ConsoleE2eReviewCommentCall = {
|
|
@@ -7,9 +7,9 @@ const meta: Meta<typeof ConsoleTabList> = {
|
|
|
7
7
|
title: 'Console/ConsoleTabList',
|
|
8
8
|
component: ConsoleTabList,
|
|
9
9
|
args: {
|
|
10
|
-
pjcode: '
|
|
10
|
+
pjcode: 'acme',
|
|
11
11
|
generatedAt: '2026-06-19T08:42:11.000Z',
|
|
12
|
-
tabHref: (tab: ConsoleTabName) => `/projects/
|
|
12
|
+
tabHref: (tab: ConsoleTabName) => `/projects/acme/${tab}`,
|
|
13
13
|
onSelectTab: () => {},
|
|
14
14
|
},
|
|
15
15
|
};
|
|
@@ -13,9 +13,9 @@ const counts: Record<ConsoleTabName, number> = {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const baseProps = {
|
|
16
|
-
pjcode: '
|
|
16
|
+
pjcode: 'acme',
|
|
17
17
|
generatedAt: '2026-06-19T08:42:11.000Z',
|
|
18
|
-
tabHref: (tab: ConsoleTabName) => `/projects/
|
|
18
|
+
tabHref: (tab: ConsoleTabName) => `/projects/acme/${tab}`,
|
|
19
19
|
onSelectTab: () => {},
|
|
20
20
|
};
|
|
21
21
|
|
|
@@ -87,7 +87,7 @@ describe('ConsoleTabList', () => {
|
|
|
87
87
|
const { getByText } = render(
|
|
88
88
|
<ConsoleTabList {...baseProps} activeTab="prs" counts={counts} />,
|
|
89
89
|
);
|
|
90
|
-
expect(getByText('
|
|
90
|
+
expect(getByText('acme')).toBeInTheDocument();
|
|
91
91
|
expect(getByText('snapshot: 2026-06-19T08:42:11.000Z')).toBeInTheDocument();
|
|
92
92
|
});
|
|
93
93
|
|