github-issue-tower-defence-management 1.185.1 → 1.186.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/CHANGELOG.md +10 -0
- package/README.md +2 -0
- package/bin/adapter/entry-points/cli/index.js +2 -1
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleOperationApi.js +43 -1
- package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleProjectResolver.js +7 -1
- package/bin/adapter/entry-points/console/consoleProjectResolver.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-wj6o8qvo.js +86 -0
- package/bin/adapter/entry-points/console/ui-dist/assets/index-yZy1TUOo.css +2 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +2 -2
- package/bin/adapter/entry-points/console/webServer.js +3 -0
- package/bin/adapter/entry-points/console/webServer.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +5 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/tokenStatusWriter.js +1 -0
- package/bin/adapter/entry-points/handlers/tokenStatusWriter.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +36 -0
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js +12 -0
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js.map +1 -1
- package/bin/domain/entities/Project.js +11 -1
- package/bin/domain/entities/Project.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +12 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/IssueNoStatusUpdateUseCase.js +36 -0
- package/bin/domain/usecases/IssueNoStatusUpdateUseCase.js.map +1 -0
- package/bin/domain/usecases/IssueRejectionEvaluator.js +19 -0
- package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
- package/bin/domain/usecases/QualityCheckAdvanceUseCase.js +1 -0
- package/bin/domain/usecases/QualityCheckAdvanceUseCase.js.map +1 -1
- package/bin/domain/usecases/ReopenedDoneIssueRevertUseCase.js +36 -0
- package/bin/domain/usecases/ReopenedDoneIssueRevertUseCase.js.map +1 -0
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +2 -5
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.ts +14 -10
- package/src/adapter/entry-points/console/consoleOperationApi.test.ts +199 -0
- package/src/adapter/entry-points/console/consoleOperationApi.ts +65 -0
- package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +26 -0
- package/src/adapter/entry-points/console/consoleProjectResolver.ts +8 -1
- package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +1 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +40 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +18 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.stories.tsx +4 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.test.tsx +114 -141
- package/src/adapter/entry-points/console/ui/src/features/console/components/list/ConsoleStoryList.tsx +84 -3
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +23 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +73 -0
- package/src/adapter/entry-points/console/ui/src/index.css +35 -0
- package/src/adapter/entry-points/console/ui-dist/assets/index-wj6o8qvo.js +86 -0
- package/src/adapter/entry-points/console/ui-dist/assets/index-yZy1TUOo.css +2 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +2 -2
- package/src/adapter/entry-points/console/webServer.ts +7 -0
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +11 -0
- package/src/adapter/entry-points/handlers/InTmuxByHumanSessionTokenCountHandler.test.ts +1 -0
- package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +1 -0
- package/src/adapter/entry-points/handlers/inTmuxByHumanDataWriter.test.ts +1 -0
- package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.test.ts +1 -0
- package/src/adapter/entry-points/handlers/notifySilentTmuxSessions.test.ts +1 -0
- package/src/adapter/entry-points/handlers/ownerCallFileCleaner.test.ts +1 -0
- package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +1 -0
- package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.test.ts +1 -0
- package/src/adapter/entry-points/handlers/tokenStatusWriter.test.ts +1 -0
- package/src/adapter/entry-points/handlers/tokenStatusWriter.ts +1 -0
- package/src/adapter/repositories/GitHubIssueCommentRepository.test.ts +1 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +90 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +48 -2
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +107 -0
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +20 -0
- package/src/adapter/repositories/issue/RestIssueRepository.test.ts +1 -0
- package/src/domain/entities/Issue.ts +1 -0
- package/src/domain/entities/Project.ts +11 -2
- package/src/domain/usecases/AgentDesignationLabelAdoptUseCase.test.ts +1 -0
- package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +1 -0
- package/src/domain/usecases/GetStoryObjectMapUseCase.test.ts +1 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +144 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +16 -0
- package/src/domain/usecases/InTmuxByHumanSessionTokenCountUseCase.test.ts +1 -0
- package/src/domain/usecases/IssueNoStatusUpdateUseCase.test.ts +165 -0
- package/src/domain/usecases/IssueNoStatusUpdateUseCase.ts +44 -0
- package/src/domain/usecases/IssueRejectionEvaluator.test.ts +120 -0
- package/src/domain/usecases/IssueRejectionEvaluator.ts +21 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +1 -0
- package/src/domain/usecases/NotifySilentLiveSessionsUseCase.test.ts +1 -0
- package/src/domain/usecases/QualityCheckAdvanceUseCase.test.ts +34 -0
- package/src/domain/usecases/QualityCheckAdvanceUseCase.ts +1 -0
- package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.test.ts +188 -0
- package/src/domain/usecases/ReopenedDoneIssueRevertUseCase.ts +57 -0
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +44 -0
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +4 -12
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +1 -0
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +1 -0
- package/src/domain/usecases/StaleTmuxSessionKillUseCase.test.ts +1 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +1 -0
- package/src/domain/usecases/TriagerApprovalDispatchUseCase.test.ts +1 -0
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +5 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +1 -0
- package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.test.ts +1 -0
- package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +1 -0
- package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +1 -0
- package/src/domain/usecases/issueReactivationTriggerIsPending.test.ts +1 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts +2 -0
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleProjectResolver.d.ts +1 -0
- package/types/adapter/entry-points/console/consoleProjectResolver.d.ts.map +1 -1
- package/types/adapter/entry-points/console/webServer.d.ts +2 -0
- 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/entry-points/handlers/tokenStatusWriter.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +4 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts +1 -0
- package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts.map +1 -1
- package/types/domain/entities/Issue.d.ts +1 -0
- package/types/domain/entities/Issue.d.ts.map +1 -1
- package/types/domain/entities/Project.d.ts +2 -1
- package/types/domain/entities/Project.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +6 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/IssueNoStatusUpdateUseCase.d.ts +12 -0
- package/types/domain/usecases/IssueNoStatusUpdateUseCase.d.ts.map +1 -0
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts +1 -0
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
- package/types/domain/usecases/QualityCheckAdvanceUseCase.d.ts.map +1 -1
- package/types/domain/usecases/ReopenedDoneIssueRevertUseCase.d.ts +12 -0
- package/types/domain/usecases/ReopenedDoneIssueRevertUseCase.d.ts.map +1 -0
- package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +3 -0
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-8Ni5iu-C.css +0 -2
- package/bin/adapter/entry-points/console/ui-dist/assets/index-DQT4HL0G.js +0 -86
- package/src/adapter/entry-points/console/ui-dist/assets/index-8Ni5iu-C.css +0 -2
- package/src/adapter/entry-points/console/ui-dist/assets/index-DQT4HL0G.js +0 -86
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Issue } from '../../../domain/entities/Issue';
|
|
2
2
|
import {
|
|
3
3
|
buildStoryListWithNew,
|
|
4
|
+
FIELD_OPTION_COLORS,
|
|
4
5
|
type FieldOption,
|
|
5
6
|
type Project,
|
|
6
7
|
} from '../../../domain/entities/Project';
|
|
@@ -52,6 +53,8 @@ export type ConsoleOperationContext = {
|
|
|
52
53
|
| null;
|
|
53
54
|
resolveProjectRepository: ConsoleProjectRepositoryResolver | null;
|
|
54
55
|
invalidateProject: ((pjcode: string) => void) | null;
|
|
56
|
+
updateProjectCacheEntry:
|
|
57
|
+
((pjcode: string, updatedProject: Project) => void) | null;
|
|
55
58
|
};
|
|
56
59
|
|
|
57
60
|
export type ConsoleOperationResponse = {
|
|
@@ -138,6 +141,7 @@ const projectItemReference = (
|
|
|
138
141
|
author: '',
|
|
139
142
|
closingIssueReferenceUrls: [],
|
|
140
143
|
agent: null,
|
|
144
|
+
stateReason: null,
|
|
141
145
|
});
|
|
142
146
|
|
|
143
147
|
const recordDone = (
|
|
@@ -698,6 +702,67 @@ export const handleReviewComment = async (
|
|
|
698
702
|
return ok();
|
|
699
703
|
};
|
|
700
704
|
|
|
705
|
+
const isStoryColor = (value: unknown): value is FieldOption['color'] =>
|
|
706
|
+
FIELD_OPTION_COLORS.some((c) => c === value);
|
|
707
|
+
|
|
708
|
+
export const handleStoryColor = async (
|
|
709
|
+
context: ConsoleOperationContext,
|
|
710
|
+
body: Record<string, unknown>,
|
|
711
|
+
): Promise<ConsoleOperationResponse> => {
|
|
712
|
+
const storyOptionId = body.storyOptionId;
|
|
713
|
+
const newColor = body.newColor;
|
|
714
|
+
const nameWithOwner = body.nameWithOwner;
|
|
715
|
+
|
|
716
|
+
if (!isNonEmptyString(storyOptionId)) {
|
|
717
|
+
return badRequest('storyOptionId is required');
|
|
718
|
+
}
|
|
719
|
+
if (!isStoryColor(newColor)) {
|
|
720
|
+
return badRequest(
|
|
721
|
+
'newColor must be one of GRAY, BLUE, GREEN, YELLOW, ORANGE, RED, PINK, PURPLE',
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
if (!isNonEmptyString(nameWithOwner)) {
|
|
725
|
+
return badRequest('nameWithOwner is required');
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const binding = await resolveBinding(context, body);
|
|
729
|
+
if (isOperationResponse(binding)) {
|
|
730
|
+
return binding;
|
|
731
|
+
}
|
|
732
|
+
const { project, pjcode } = binding;
|
|
733
|
+
|
|
734
|
+
if (project.story === null) {
|
|
735
|
+
return badRequest('project does not have a story field');
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const storyOption = project.story.stories.find((s) => s.id === storyOptionId);
|
|
739
|
+
if (storyOption === undefined) {
|
|
740
|
+
return badRequest(`story option "${storyOptionId}" not found in project`);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const proxyUrl = `https://github.com/${nameWithOwner}/issues/0`;
|
|
744
|
+
await context
|
|
745
|
+
.resolveIssueRepository(proxyUrl)
|
|
746
|
+
.updateStoryOptionColor(
|
|
747
|
+
{ ...project, story: project.story },
|
|
748
|
+
storyOptionId,
|
|
749
|
+
newColor,
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
if (context.updateProjectCacheEntry !== null) {
|
|
753
|
+
const updatedStories = project.story.stories.map((s) =>
|
|
754
|
+
s.id === storyOptionId ? { ...s, color: newColor } : s,
|
|
755
|
+
);
|
|
756
|
+
const updatedProject: Project = {
|
|
757
|
+
...project,
|
|
758
|
+
story: { ...project.story, stories: updatedStories },
|
|
759
|
+
};
|
|
760
|
+
context.updateProjectCacheEntry(pjcode, updatedProject);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return ok();
|
|
764
|
+
};
|
|
765
|
+
|
|
701
766
|
export const handleIntmux = async (
|
|
702
767
|
context: ConsoleOperationContext,
|
|
703
768
|
body: Record<string, unknown>,
|
|
@@ -127,6 +127,32 @@ describe('createConsoleProjectResolver', () => {
|
|
|
127
127
|
});
|
|
128
128
|
expect(loadProject).toHaveBeenCalledTimes(2);
|
|
129
129
|
});
|
|
130
|
+
|
|
131
|
+
it('updateEntry replaces the cached project so the next resolve returns the updated project', async () => {
|
|
132
|
+
const updatedProject: Project = { ...acmeProject, id: 'PVT_acme_updated' };
|
|
133
|
+
const loadProject = jest.fn(async () => acmeProject);
|
|
134
|
+
const { resolve, updateEntry } = createConsoleProjectResolver(
|
|
135
|
+
{ acme: 'https://github.com/orgs/acme/projects/1' },
|
|
136
|
+
loadProject,
|
|
137
|
+
);
|
|
138
|
+
await resolve('acme');
|
|
139
|
+
updateEntry('acme', updatedProject);
|
|
140
|
+
const result = await resolve('acme');
|
|
141
|
+
expect(result?.project).toBe(updatedProject);
|
|
142
|
+
expect(loadProject).toHaveBeenCalledTimes(1);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('updateEntry is a no-op for a pjcode that was never resolved', async () => {
|
|
146
|
+
const updatedProject: Project = { ...acmeProject, id: 'PVT_acme_updated' };
|
|
147
|
+
const loadProject = jest.fn(async () => acmeProject);
|
|
148
|
+
const { resolve, updateEntry } = createConsoleProjectResolver(
|
|
149
|
+
{ acme: 'https://github.com/orgs/acme/projects/1' },
|
|
150
|
+
loadProject,
|
|
151
|
+
);
|
|
152
|
+
updateEntry('acme', updatedProject);
|
|
153
|
+
const result = await resolve('acme');
|
|
154
|
+
expect(result?.project).toBe(acmeProject);
|
|
155
|
+
});
|
|
130
156
|
});
|
|
131
157
|
|
|
132
158
|
describe('createConsoleProjectLoader', () => {
|
|
@@ -71,6 +71,7 @@ export const createPjcodeConfigChecker = (
|
|
|
71
71
|
export type ConsoleProjectResolverBundle = {
|
|
72
72
|
resolve: ConsoleProjectResolver;
|
|
73
73
|
invalidate: (pjcode: string) => void;
|
|
74
|
+
updateEntry: (pjcode: string, updatedProject: Project) => void;
|
|
74
75
|
};
|
|
75
76
|
|
|
76
77
|
export const createConsoleProjectResolver = (
|
|
@@ -100,5 +101,11 @@ export const createConsoleProjectResolver = (
|
|
|
100
101
|
const invalidate = (pjcode: string): void => {
|
|
101
102
|
cache.delete(pjcode);
|
|
102
103
|
};
|
|
103
|
-
|
|
104
|
+
const updateEntry = (pjcode: string, updatedProject: Project): void => {
|
|
105
|
+
const existing = cache.get(pjcode);
|
|
106
|
+
if (existing !== undefined) {
|
|
107
|
+
cache.set(pjcode, { ...existing, project: updatedProject });
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
return { resolve, invalidate, updateEntry };
|
|
104
111
|
};
|
|
@@ -514,6 +514,46 @@ test('creates a new story when the add-story button and form are used', async ({
|
|
|
514
514
|
).toHaveCount(0);
|
|
515
515
|
});
|
|
516
516
|
|
|
517
|
+
test('changes the color of a story row via the color palette in the stories tab', async ({
|
|
518
|
+
page,
|
|
519
|
+
}) => {
|
|
520
|
+
await page.goto(harness.appRootUrl);
|
|
521
|
+
|
|
522
|
+
await tabByLabel(page, 'Stories').click();
|
|
523
|
+
|
|
524
|
+
const tdpmRow = page.locator('.console-story-list-row', {
|
|
525
|
+
hasText: 'TDPM Console port',
|
|
526
|
+
});
|
|
527
|
+
await expect(tdpmRow).toBeVisible();
|
|
528
|
+
|
|
529
|
+
const changeColorButton = tdpmRow.locator('.console-op-button', {
|
|
530
|
+
hasText: 'Change color',
|
|
531
|
+
});
|
|
532
|
+
await expect(changeColorButton).toBeVisible();
|
|
533
|
+
|
|
534
|
+
await changeColorButton.click();
|
|
535
|
+
|
|
536
|
+
const palette = tdpmRow.locator('.console-story-color-palette');
|
|
537
|
+
await expect(palette).toBeVisible();
|
|
538
|
+
|
|
539
|
+
const swatches = palette.locator('.console-story-color-swatch');
|
|
540
|
+
await expect(swatches).toHaveCount(8);
|
|
541
|
+
|
|
542
|
+
const graySwatch = swatches.filter({ hasText: 'disable' });
|
|
543
|
+
await expect(graySwatch).toHaveCount(1);
|
|
544
|
+
|
|
545
|
+
const greenSwatch = palette.locator('[aria-label="GREEN"]');
|
|
546
|
+
await greenSwatch.click();
|
|
547
|
+
|
|
548
|
+
await expect
|
|
549
|
+
.poll(() => harness.storyColorCalls.length, { timeout: 10000 })
|
|
550
|
+
.toBe(1);
|
|
551
|
+
expect(harness.storyColorCalls[0].storyOptionId).toBe('1491051e');
|
|
552
|
+
expect(harness.storyColorCalls[0].newColor).toBe('GREEN');
|
|
553
|
+
|
|
554
|
+
await expect(palette).toHaveCount(0);
|
|
555
|
+
});
|
|
556
|
+
|
|
517
557
|
test('reorders stories on the triage tab with up/down buttons', async ({
|
|
518
558
|
page,
|
|
519
559
|
}) => {
|
|
@@ -47,6 +47,11 @@ export type ConsoleE2eCreateIssueCall = {
|
|
|
47
47
|
title: string;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
+
export type ConsoleE2eStoryColorCall = {
|
|
51
|
+
storyOptionId: string;
|
|
52
|
+
newColor: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
50
55
|
type ConsoleFixtureListItem = {
|
|
51
56
|
number: number;
|
|
52
57
|
title: string;
|
|
@@ -391,6 +396,7 @@ const buildIssueForUrl = (url: string): Issue => ({
|
|
|
391
396
|
author: 'HiromiShikata',
|
|
392
397
|
closingIssueReferenceUrls: [],
|
|
393
398
|
agent: null,
|
|
399
|
+
stateReason: null,
|
|
394
400
|
});
|
|
395
401
|
|
|
396
402
|
const notImplemented = (method: string): never => {
|
|
@@ -453,6 +459,7 @@ const createStubIssueRepository = (
|
|
|
453
459
|
reviewCommentCalls: ConsoleE2eReviewCommentCall[],
|
|
454
460
|
requestChangesCalls: ConsoleE2eRequestChangesCall[],
|
|
455
461
|
createIssueCalls: ConsoleE2eCreateIssueCall[],
|
|
462
|
+
storyColorCalls: ConsoleE2eStoryColorCall[],
|
|
456
463
|
): IssueRepository => ({
|
|
457
464
|
getAllIssues: () => notImplemented('getAllIssues'),
|
|
458
465
|
getIssueByUrl: async (url: string): Promise<Issue | null> =>
|
|
@@ -615,6 +622,13 @@ const createStubIssueRepository = (
|
|
|
615
622
|
}
|
|
616
623
|
: null,
|
|
617
624
|
setIssueAgentField: async (): Promise<void> => undefined,
|
|
625
|
+
updateStoryOptionColor: async (
|
|
626
|
+
_project,
|
|
627
|
+
storyOptionId: string,
|
|
628
|
+
newColor: string,
|
|
629
|
+
): Promise<void> => {
|
|
630
|
+
storyColorCalls.push({ storyOptionId, newColor });
|
|
631
|
+
},
|
|
618
632
|
});
|
|
619
633
|
|
|
620
634
|
export type ConsoleE2eReorderStoryCall = {
|
|
@@ -635,6 +649,7 @@ export type ConsoleE2eHarness = {
|
|
|
635
649
|
createIssueCalls: ConsoleE2eCreateIssueCall[];
|
|
636
650
|
reorderStoryCalls: ConsoleE2eReorderStoryCall[];
|
|
637
651
|
addStoryCalls: ConsoleE2eAddStoryCall[];
|
|
652
|
+
storyColorCalls: ConsoleE2eStoryColorCall[];
|
|
638
653
|
stop: () => Promise<void>;
|
|
639
654
|
};
|
|
640
655
|
|
|
@@ -658,6 +673,7 @@ export const startConsoleE2eHarness = async (): Promise<ConsoleE2eHarness> => {
|
|
|
658
673
|
const createIssueCalls: ConsoleE2eCreateIssueCall[] = [];
|
|
659
674
|
const reorderStoryCalls: ConsoleE2eReorderStoryCall[] = [];
|
|
660
675
|
const addStoryCalls: ConsoleE2eAddStoryCall[] = [];
|
|
676
|
+
const storyColorCalls: ConsoleE2eStoryColorCall[] = [];
|
|
661
677
|
|
|
662
678
|
const server = await startWebServer({
|
|
663
679
|
accessToken: CONSOLE_E2E_TOKEN,
|
|
@@ -667,6 +683,7 @@ export const startConsoleE2eHarness = async (): Promise<ConsoleE2eHarness> => {
|
|
|
667
683
|
reviewCommentCalls,
|
|
668
684
|
requestChangesCalls,
|
|
669
685
|
createIssueCalls,
|
|
686
|
+
storyColorCalls,
|
|
670
687
|
),
|
|
671
688
|
projectRepository: {
|
|
672
689
|
updateStoryList: async (_updatedProject, stories) => {
|
|
@@ -726,6 +743,7 @@ export const startConsoleE2eHarness = async (): Promise<ConsoleE2eHarness> => {
|
|
|
726
743
|
createIssueCalls,
|
|
727
744
|
reorderStoryCalls,
|
|
728
745
|
addStoryCalls,
|
|
746
|
+
storyColorCalls,
|
|
729
747
|
stop: async (): Promise<void> => {
|
|
730
748
|
await closeServer(server);
|
|
731
749
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
@@ -53,6 +53,10 @@ const meta: Meta<typeof ConsoleStoryList> = {
|
|
|
53
53
|
args: {
|
|
54
54
|
onCreateIssue: () => Promise.resolve(),
|
|
55
55
|
onAddStory: () => Promise.resolve(),
|
|
56
|
+
onSelectColor: () => undefined,
|
|
57
|
+
optimisticColors: {},
|
|
58
|
+
colorChangeInFlight: null,
|
|
59
|
+
colorErrors: {},
|
|
56
60
|
},
|
|
57
61
|
};
|
|
58
62
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fireEvent, render, waitFor } from '@testing-library/react';
|
|
2
|
-
import type { ConsoleStoryEntry } from '../../logic/types';
|
|
2
|
+
import type { ConsoleColor, ConsoleStoryEntry } from '../../logic/types';
|
|
3
3
|
import { ConsoleStoryList } from './ConsoleStoryList';
|
|
4
4
|
|
|
5
5
|
const storyEntries: ConsoleStoryEntry[] = [
|
|
@@ -20,20 +20,20 @@ const storyEntries: ConsoleStoryEntry[] = [
|
|
|
20
20
|
];
|
|
21
21
|
|
|
22
22
|
const defaultProps = {
|
|
23
|
+
stories: storyEntries,
|
|
24
|
+
isLoading: false,
|
|
25
|
+
error: null,
|
|
23
26
|
onCreateIssue: () => Promise.resolve(),
|
|
24
27
|
onAddStory: () => Promise.resolve(),
|
|
28
|
+
onSelectColor: () => undefined,
|
|
29
|
+
optimisticColors: {} as Record<string, ConsoleColor>,
|
|
30
|
+
colorChangeInFlight: null as string | null,
|
|
31
|
+
colorErrors: {} as Record<string, string>,
|
|
25
32
|
};
|
|
26
33
|
|
|
27
34
|
describe('ConsoleStoryList', () => {
|
|
28
35
|
it('renders each story name and its open item count', () => {
|
|
29
|
-
const { getByText } = render(
|
|
30
|
-
<ConsoleStoryList
|
|
31
|
-
stories={storyEntries}
|
|
32
|
-
isLoading={false}
|
|
33
|
-
error={null}
|
|
34
|
-
{...defaultProps}
|
|
35
|
-
/>,
|
|
36
|
-
);
|
|
36
|
+
const { getByText } = render(<ConsoleStoryList {...defaultProps} />);
|
|
37
37
|
expect(getByText('TDPM Console port')).toBeInTheDocument();
|
|
38
38
|
expect(getByText('12')).toBeInTheDocument();
|
|
39
39
|
expect(getByText('Move to Okinawa')).toBeInTheDocument();
|
|
@@ -41,26 +41,14 @@ describe('ConsoleStoryList', () => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
it('renders an Add task button for each story row', () => {
|
|
44
|
-
const { getAllByRole } = render(
|
|
45
|
-
<ConsoleStoryList
|
|
46
|
-
stories={storyEntries}
|
|
47
|
-
isLoading={false}
|
|
48
|
-
error={null}
|
|
49
|
-
{...defaultProps}
|
|
50
|
-
/>,
|
|
51
|
-
);
|
|
44
|
+
const { getAllByRole } = render(<ConsoleStoryList {...defaultProps} />);
|
|
52
45
|
const buttons = getAllByRole('button', { name: 'Add task' });
|
|
53
46
|
expect(buttons).toHaveLength(storyEntries.length);
|
|
54
47
|
});
|
|
55
48
|
|
|
56
49
|
it('shows the create form when Add task is clicked', () => {
|
|
57
50
|
const { getAllByRole, getByPlaceholderText } = render(
|
|
58
|
-
<ConsoleStoryList
|
|
59
|
-
stories={storyEntries}
|
|
60
|
-
isLoading={false}
|
|
61
|
-
error={null}
|
|
62
|
-
{...defaultProps}
|
|
63
|
-
/>,
|
|
51
|
+
<ConsoleStoryList {...defaultProps} />,
|
|
64
52
|
);
|
|
65
53
|
const [firstButton] = getAllByRole('button', { name: 'Add task' });
|
|
66
54
|
fireEvent.click(firstButton);
|
|
@@ -69,12 +57,7 @@ describe('ConsoleStoryList', () => {
|
|
|
69
57
|
|
|
70
58
|
it('hides the form when Add task is clicked again on the same row', () => {
|
|
71
59
|
const { getAllByRole, queryByPlaceholderText } = render(
|
|
72
|
-
<ConsoleStoryList
|
|
73
|
-
stories={storyEntries}
|
|
74
|
-
isLoading={false}
|
|
75
|
-
error={null}
|
|
76
|
-
{...defaultProps}
|
|
77
|
-
/>,
|
|
60
|
+
<ConsoleStoryList {...defaultProps} />,
|
|
78
61
|
);
|
|
79
62
|
const [firstButton] = getAllByRole('button', { name: 'Add task' });
|
|
80
63
|
fireEvent.click(firstButton);
|
|
@@ -85,13 +68,7 @@ describe('ConsoleStoryList', () => {
|
|
|
85
68
|
it('calls onCreateIssue with the correct storyOptionId and title', async () => {
|
|
86
69
|
const onCreateIssue = jest.fn().mockResolvedValue(undefined);
|
|
87
70
|
const { getAllByRole, getByPlaceholderText, getByRole } = render(
|
|
88
|
-
<ConsoleStoryList
|
|
89
|
-
stories={storyEntries}
|
|
90
|
-
isLoading={false}
|
|
91
|
-
error={null}
|
|
92
|
-
onCreateIssue={onCreateIssue}
|
|
93
|
-
onAddStory={() => Promise.resolve()}
|
|
94
|
-
/>,
|
|
71
|
+
<ConsoleStoryList {...defaultProps} onCreateIssue={onCreateIssue} />,
|
|
95
72
|
);
|
|
96
73
|
fireEvent.click(getAllByRole('button', { name: 'Add task' })[0]);
|
|
97
74
|
fireEvent.change(getByPlaceholderText('Issue title'), {
|
|
@@ -108,12 +85,7 @@ describe('ConsoleStoryList', () => {
|
|
|
108
85
|
|
|
109
86
|
it('shows a validation error when Create is clicked with an empty title', () => {
|
|
110
87
|
const { getAllByRole, getByRole, getByText } = render(
|
|
111
|
-
<ConsoleStoryList
|
|
112
|
-
stories={storyEntries}
|
|
113
|
-
isLoading={false}
|
|
114
|
-
error={null}
|
|
115
|
-
{...defaultProps}
|
|
116
|
-
/>,
|
|
88
|
+
<ConsoleStoryList {...defaultProps} />,
|
|
117
89
|
);
|
|
118
90
|
fireEvent.click(getAllByRole('button', { name: 'Add task' })[0]);
|
|
119
91
|
fireEvent.click(getByRole('button', { name: 'Create' }));
|
|
@@ -128,13 +100,7 @@ describe('ConsoleStoryList', () => {
|
|
|
128
100
|
getByRole,
|
|
129
101
|
queryByPlaceholderText,
|
|
130
102
|
} = render(
|
|
131
|
-
<ConsoleStoryList
|
|
132
|
-
stories={storyEntries}
|
|
133
|
-
isLoading={false}
|
|
134
|
-
error={null}
|
|
135
|
-
onCreateIssue={onCreateIssue}
|
|
136
|
-
onAddStory={() => Promise.resolve()}
|
|
137
|
-
/>,
|
|
103
|
+
<ConsoleStoryList {...defaultProps} onCreateIssue={onCreateIssue} />,
|
|
138
104
|
);
|
|
139
105
|
fireEvent.click(getAllByRole('button', { name: 'Add task' })[0]);
|
|
140
106
|
fireEvent.change(getByPlaceholderText('Issue title'), {
|
|
@@ -151,13 +117,7 @@ describe('ConsoleStoryList', () => {
|
|
|
151
117
|
.fn()
|
|
152
118
|
.mockRejectedValue(new Error('Network error'));
|
|
153
119
|
const { getAllByRole, getByPlaceholderText, getByRole, getByText } = render(
|
|
154
|
-
<ConsoleStoryList
|
|
155
|
-
stories={storyEntries}
|
|
156
|
-
isLoading={false}
|
|
157
|
-
error={null}
|
|
158
|
-
onCreateIssue={onCreateIssue}
|
|
159
|
-
onAddStory={() => Promise.resolve()}
|
|
160
|
-
/>,
|
|
120
|
+
<ConsoleStoryList {...defaultProps} onCreateIssue={onCreateIssue} />,
|
|
161
121
|
);
|
|
162
122
|
fireEvent.click(getAllByRole('button', { name: 'Add task' })[0]);
|
|
163
123
|
fireEvent.change(getByPlaceholderText('Issue title'), {
|
|
@@ -169,36 +129,21 @@ describe('ConsoleStoryList', () => {
|
|
|
169
129
|
|
|
170
130
|
it('shows a loading message when isLoading is true', () => {
|
|
171
131
|
const { getByText } = render(
|
|
172
|
-
<ConsoleStoryList
|
|
173
|
-
stories={[]}
|
|
174
|
-
isLoading={true}
|
|
175
|
-
error={null}
|
|
176
|
-
{...defaultProps}
|
|
177
|
-
/>,
|
|
132
|
+
<ConsoleStoryList {...defaultProps} stories={[]} isLoading={true} />,
|
|
178
133
|
);
|
|
179
134
|
expect(getByText('Loading stories...')).toBeInTheDocument();
|
|
180
135
|
});
|
|
181
136
|
|
|
182
137
|
it('shows an empty message when there are no stories', () => {
|
|
183
138
|
const { getByText } = render(
|
|
184
|
-
<ConsoleStoryList
|
|
185
|
-
stories={[]}
|
|
186
|
-
isLoading={false}
|
|
187
|
-
error={null}
|
|
188
|
-
{...defaultProps}
|
|
189
|
-
/>,
|
|
139
|
+
<ConsoleStoryList {...defaultProps} stories={[]} />,
|
|
190
140
|
);
|
|
191
141
|
expect(getByText('No active stories')).toBeInTheDocument();
|
|
192
142
|
});
|
|
193
143
|
|
|
194
144
|
it('shows an error message when error is set', () => {
|
|
195
145
|
const { getByRole, getByText } = render(
|
|
196
|
-
<ConsoleStoryList
|
|
197
|
-
stories={[]}
|
|
198
|
-
isLoading={false}
|
|
199
|
-
error="HTTP 503"
|
|
200
|
-
{...defaultProps}
|
|
201
|
-
/>,
|
|
146
|
+
<ConsoleStoryList {...defaultProps} stories={[]} error="HTTP 503" />,
|
|
202
147
|
);
|
|
203
148
|
expect(getByRole('alert')).toBeInTheDocument();
|
|
204
149
|
expect(getByText(/HTTP 503/)).toBeInTheDocument();
|
|
@@ -212,28 +157,14 @@ describe('ConsoleStoryList', () => {
|
|
|
212
157
|
openItemCount: 4,
|
|
213
158
|
} as unknown as ConsoleStoryEntry;
|
|
214
159
|
const { getByText } = render(
|
|
215
|
-
<ConsoleStoryList
|
|
216
|
-
stories={[oldFormatEntry]}
|
|
217
|
-
isLoading={false}
|
|
218
|
-
error={null}
|
|
219
|
-
onCreateIssue={() => Promise.resolve()}
|
|
220
|
-
onAddStory={() => Promise.resolve()}
|
|
221
|
-
/>,
|
|
160
|
+
<ConsoleStoryList {...defaultProps} stories={[oldFormatEntry]} />,
|
|
222
161
|
);
|
|
223
162
|
const nameEl = getByText('TDPM Console port');
|
|
224
163
|
expect(nameEl.tagName).toBe('SPAN');
|
|
225
164
|
});
|
|
226
165
|
|
|
227
166
|
it('renders story name as a span when storyViewUrl is null', () => {
|
|
228
|
-
const { getByText } = render(
|
|
229
|
-
<ConsoleStoryList
|
|
230
|
-
stories={storyEntries}
|
|
231
|
-
isLoading={false}
|
|
232
|
-
error={null}
|
|
233
|
-
onCreateIssue={() => Promise.resolve()}
|
|
234
|
-
onAddStory={() => Promise.resolve()}
|
|
235
|
-
/>,
|
|
236
|
-
);
|
|
167
|
+
const { getByText } = render(<ConsoleStoryList {...defaultProps} />);
|
|
237
168
|
const nameEl = getByText('TDPM Console port');
|
|
238
169
|
expect(nameEl.tagName).toBe('SPAN');
|
|
239
170
|
});
|
|
@@ -247,13 +178,7 @@ describe('ConsoleStoryList', () => {
|
|
|
247
178
|
},
|
|
248
179
|
];
|
|
249
180
|
const { getByText } = render(
|
|
250
|
-
<ConsoleStoryList
|
|
251
|
-
stories={entriesWithUrl}
|
|
252
|
-
isLoading={false}
|
|
253
|
-
error={null}
|
|
254
|
-
onCreateIssue={() => Promise.resolve()}
|
|
255
|
-
onAddStory={() => Promise.resolve()}
|
|
256
|
-
/>,
|
|
181
|
+
<ConsoleStoryList {...defaultProps} stories={entriesWithUrl} />,
|
|
257
182
|
);
|
|
258
183
|
const nameEl = getByText('TDPM Console port');
|
|
259
184
|
expect(nameEl.tagName).toBe('A');
|
|
@@ -266,25 +191,13 @@ describe('ConsoleStoryList', () => {
|
|
|
266
191
|
});
|
|
267
192
|
|
|
268
193
|
it('renders an Add story button', () => {
|
|
269
|
-
const { getByRole } = render(
|
|
270
|
-
<ConsoleStoryList
|
|
271
|
-
stories={storyEntries}
|
|
272
|
-
isLoading={false}
|
|
273
|
-
error={null}
|
|
274
|
-
{...defaultProps}
|
|
275
|
-
/>,
|
|
276
|
-
);
|
|
194
|
+
const { getByRole } = render(<ConsoleStoryList {...defaultProps} />);
|
|
277
195
|
expect(getByRole('button', { name: 'Add story' })).toBeInTheDocument();
|
|
278
196
|
});
|
|
279
197
|
|
|
280
198
|
it('shows the Add story form when Add story button is clicked', () => {
|
|
281
199
|
const { getByRole, getByPlaceholderText } = render(
|
|
282
|
-
<ConsoleStoryList
|
|
283
|
-
stories={storyEntries}
|
|
284
|
-
isLoading={false}
|
|
285
|
-
error={null}
|
|
286
|
-
{...defaultProps}
|
|
287
|
-
/>,
|
|
200
|
+
<ConsoleStoryList {...defaultProps} />,
|
|
288
201
|
);
|
|
289
202
|
fireEvent.click(getByRole('button', { name: 'Add story' }));
|
|
290
203
|
expect(getByPlaceholderText('Story name')).toBeInTheDocument();
|
|
@@ -292,12 +205,7 @@ describe('ConsoleStoryList', () => {
|
|
|
292
205
|
|
|
293
206
|
it('hides the Add story form when Add story button is clicked again', () => {
|
|
294
207
|
const { getByRole, queryByPlaceholderText } = render(
|
|
295
|
-
<ConsoleStoryList
|
|
296
|
-
stories={storyEntries}
|
|
297
|
-
isLoading={false}
|
|
298
|
-
error={null}
|
|
299
|
-
{...defaultProps}
|
|
300
|
-
/>,
|
|
208
|
+
<ConsoleStoryList {...defaultProps} />,
|
|
301
209
|
);
|
|
302
210
|
fireEvent.click(getByRole('button', { name: 'Add story' }));
|
|
303
211
|
fireEvent.click(getByRole('button', { name: 'Add story' }));
|
|
@@ -307,13 +215,7 @@ describe('ConsoleStoryList', () => {
|
|
|
307
215
|
it('calls onAddStory with the story name and closes the form on success', async () => {
|
|
308
216
|
const onAddStory = jest.fn().mockResolvedValue(undefined);
|
|
309
217
|
const { getByRole, getByPlaceholderText, queryByPlaceholderText } = render(
|
|
310
|
-
<ConsoleStoryList
|
|
311
|
-
stories={storyEntries}
|
|
312
|
-
isLoading={false}
|
|
313
|
-
error={null}
|
|
314
|
-
onCreateIssue={() => Promise.resolve()}
|
|
315
|
-
onAddStory={onAddStory}
|
|
316
|
-
/>,
|
|
218
|
+
<ConsoleStoryList {...defaultProps} onAddStory={onAddStory} />,
|
|
317
219
|
);
|
|
318
220
|
fireEvent.click(getByRole('button', { name: 'Add story' }));
|
|
319
221
|
fireEvent.change(getByPlaceholderText('Story name'), {
|
|
@@ -330,12 +232,7 @@ describe('ConsoleStoryList', () => {
|
|
|
330
232
|
|
|
331
233
|
it('shows a validation error when Add story Create is clicked with an empty name', () => {
|
|
332
234
|
const { getByRole, getByText } = render(
|
|
333
|
-
<ConsoleStoryList
|
|
334
|
-
stories={storyEntries}
|
|
335
|
-
isLoading={false}
|
|
336
|
-
error={null}
|
|
337
|
-
{...defaultProps}
|
|
338
|
-
/>,
|
|
235
|
+
<ConsoleStoryList {...defaultProps} />,
|
|
339
236
|
);
|
|
340
237
|
fireEvent.click(getByRole('button', { name: 'Add story' }));
|
|
341
238
|
fireEvent.click(getByRole('button', { name: 'Create' }));
|
|
@@ -345,13 +242,7 @@ describe('ConsoleStoryList', () => {
|
|
|
345
242
|
it('shows an API error when onAddStory rejects', async () => {
|
|
346
243
|
const onAddStory = jest.fn().mockRejectedValue(new Error('API failure'));
|
|
347
244
|
const { getByRole, getByPlaceholderText, getByText } = render(
|
|
348
|
-
<ConsoleStoryList
|
|
349
|
-
stories={storyEntries}
|
|
350
|
-
isLoading={false}
|
|
351
|
-
error={null}
|
|
352
|
-
onCreateIssue={() => Promise.resolve()}
|
|
353
|
-
onAddStory={onAddStory}
|
|
354
|
-
/>,
|
|
245
|
+
<ConsoleStoryList {...defaultProps} onAddStory={onAddStory} />,
|
|
355
246
|
);
|
|
356
247
|
fireEvent.click(getByRole('button', { name: 'Add story' }));
|
|
357
248
|
fireEvent.change(getByPlaceholderText('Story name'), {
|
|
@@ -363,13 +254,95 @@ describe('ConsoleStoryList', () => {
|
|
|
363
254
|
|
|
364
255
|
it('renders Add story button even when there are no stories', () => {
|
|
365
256
|
const { getByRole } = render(
|
|
257
|
+
<ConsoleStoryList {...defaultProps} stories={[]} />,
|
|
258
|
+
);
|
|
259
|
+
expect(getByRole('button', { name: 'Add story' })).toBeInTheDocument();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('renders a Change color button for each story row', () => {
|
|
263
|
+
const { getAllByRole } = render(<ConsoleStoryList {...defaultProps} />);
|
|
264
|
+
const buttons = getAllByRole('button', { name: 'Change color' });
|
|
265
|
+
expect(buttons).toHaveLength(storyEntries.length);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('shows the color palette when Change color is clicked', () => {
|
|
269
|
+
const { getAllByRole } = render(<ConsoleStoryList {...defaultProps} />);
|
|
270
|
+
const [firstColorButton] = getAllByRole('button', { name: 'Change color' });
|
|
271
|
+
fireEvent.click(firstColorButton);
|
|
272
|
+
expect(getAllByRole('button', { name: /GRAY \(disable\)/ })).toHaveLength(
|
|
273
|
+
1,
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('hides the palette when Change color is clicked again on the same row', () => {
|
|
278
|
+
const { getAllByRole, queryByRole } = render(
|
|
279
|
+
<ConsoleStoryList {...defaultProps} />,
|
|
280
|
+
);
|
|
281
|
+
const [firstColorButton] = getAllByRole('button', { name: 'Change color' });
|
|
282
|
+
fireEvent.click(firstColorButton);
|
|
283
|
+
fireEvent.click(firstColorButton);
|
|
284
|
+
expect(queryByRole('button', { name: /GRAY \(disable\)/ })).toBeNull();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('calls onSelectColor with the correct storyOptionId and color when a swatch is clicked', () => {
|
|
288
|
+
const onSelectColor = jest.fn();
|
|
289
|
+
const { getAllByRole } = render(
|
|
290
|
+
<ConsoleStoryList {...defaultProps} onSelectColor={onSelectColor} />,
|
|
291
|
+
);
|
|
292
|
+
const [firstColorButton] = getAllByRole('button', { name: 'Change color' });
|
|
293
|
+
fireEvent.click(firstColorButton);
|
|
294
|
+
const greenSwatch = getAllByRole('button', { name: 'GREEN' })[0];
|
|
295
|
+
fireEvent.click(greenSwatch);
|
|
296
|
+
expect(onSelectColor).toHaveBeenCalledWith('1491051e', 'GREEN');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('closes the palette after a swatch is clicked', () => {
|
|
300
|
+
const { getAllByRole, queryByRole } = render(
|
|
301
|
+
<ConsoleStoryList {...defaultProps} />,
|
|
302
|
+
);
|
|
303
|
+
const [firstColorButton] = getAllByRole('button', { name: 'Change color' });
|
|
304
|
+
fireEvent.click(firstColorButton);
|
|
305
|
+
const greenSwatch = getAllByRole('button', { name: 'GREEN' })[0];
|
|
306
|
+
fireEvent.click(greenSwatch);
|
|
307
|
+
expect(queryByRole('button', { name: /GRAY \(disable\)/ })).toBeNull();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('GRAY swatch carries an accessible disable label', () => {
|
|
311
|
+
const { getAllByRole, getByRole } = render(
|
|
312
|
+
<ConsoleStoryList {...defaultProps} />,
|
|
313
|
+
);
|
|
314
|
+
const [firstColorButton] = getAllByRole('button', { name: 'Change color' });
|
|
315
|
+
fireEvent.click(firstColorButton);
|
|
316
|
+
expect(getByRole('button', { name: 'GRAY (disable)' })).toBeInTheDocument();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('disables Change color button when colorChangeInFlight matches that storyOptionId', () => {
|
|
320
|
+
const { getAllByRole } = render(
|
|
321
|
+
<ConsoleStoryList {...defaultProps} colorChangeInFlight="1491051e" />,
|
|
322
|
+
);
|
|
323
|
+
const colorButtons = getAllByRole('button', { name: 'Change color' });
|
|
324
|
+
expect(colorButtons[0]).toBeDisabled();
|
|
325
|
+
expect(colorButtons[1]).not.toBeDisabled();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('displays an optimistic color from optimisticColors prop', () => {
|
|
329
|
+
const { getAllByRole } = render(
|
|
366
330
|
<ConsoleStoryList
|
|
367
|
-
stories={[]}
|
|
368
|
-
isLoading={false}
|
|
369
|
-
error={null}
|
|
370
331
|
{...defaultProps}
|
|
332
|
+
optimisticColors={{ '1491051e': 'RED' }}
|
|
371
333
|
/>,
|
|
372
334
|
);
|
|
373
|
-
|
|
335
|
+
const colorButtons = getAllByRole('button', { name: 'Change color' });
|
|
336
|
+
expect(colorButtons[0]).toBeInTheDocument();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('shows a per-row error message from colorErrors prop', () => {
|
|
340
|
+
const { getByText } = render(
|
|
341
|
+
<ConsoleStoryList
|
|
342
|
+
{...defaultProps}
|
|
343
|
+
colorErrors={{ '1491051e': 'Color update failed' }}
|
|
344
|
+
/>,
|
|
345
|
+
);
|
|
346
|
+
expect(getByText('Color update failed')).toBeInTheDocument();
|
|
374
347
|
});
|
|
375
348
|
});
|