@shipfox/client-workflows 12.0.2 → 14.0.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/.storybook/main.ts +16 -1
- package/.storybook/preview.tsx +3 -3
- package/.storybook/workspace-source.ts +31 -0
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +79 -0
- package/dist/components/job-graph/job-duration-format.d.ts +2 -2
- package/dist/components/job-graph/job-duration-format.d.ts.map +1 -1
- package/dist/components/job-graph/job-duration-format.js +4 -2
- package/dist/components/job-graph/job-duration-format.js.map +1 -1
- package/dist/components/job-graph/job-duration-label.js +1 -1
- package/dist/components/job-graph/job-duration-label.js.map +1 -1
- package/dist/components/job-graph/job-graph-content.d.ts +2 -1
- package/dist/components/job-graph/job-graph-content.d.ts.map +1 -1
- package/dist/components/job-graph/job-graph-content.js +3 -3
- package/dist/components/job-graph/job-graph-content.js.map +1 -1
- package/dist/components/job-graph/job-graph-view.d.ts +2 -1
- package/dist/components/job-graph/job-graph-view.d.ts.map +1 -1
- package/dist/components/job-graph/job-graph-view.js +2 -2
- package/dist/components/job-graph/job-graph-view.js.map +1 -1
- package/dist/components/job-graph/job-node.d.ts +1 -1
- package/dist/components/job-graph/job-node.d.ts.map +1 -1
- package/dist/components/job-graph/job-node.js +10 -9
- package/dist/components/job-graph/job-node.js.map +1 -1
- package/dist/components/job-graph/types.d.ts +2 -1
- package/dist/components/job-graph/types.d.ts.map +1 -1
- package/dist/components/job-graph/types.js.map +1 -1
- package/dist/components/step-list/step-list.d.ts +2 -2
- package/dist/components/step-list/step-list.d.ts.map +1 -1
- package/dist/components/step-list/step-list.js.map +1 -1
- package/dist/components/workflow-run-duration-label.js +1 -1
- package/dist/components/workflow-run-duration-label.js.map +1 -1
- package/dist/components/workflow-run-list/job-status-strip.d.ts +21 -0
- package/dist/components/workflow-run-list/job-status-strip.d.ts.map +1 -0
- package/dist/components/workflow-run-list/job-status-strip.js +185 -0
- package/dist/components/workflow-run-list/job-status-strip.js.map +1 -0
- package/dist/components/workflow-run-list/run-display.d.ts +27 -3
- package/dist/components/workflow-run-list/run-display.d.ts.map +1 -1
- package/dist/components/workflow-run-list/run-display.js +78 -7
- package/dist/components/workflow-run-list/run-display.js.map +1 -1
- package/dist/components/workflow-run-list/types.d.ts +23 -24
- package/dist/components/workflow-run-list/types.d.ts.map +1 -1
- package/dist/components/workflow-run-list/types.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts +22 -0
- package/dist/components/workflow-run-list/workflow-run-filter-menu.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js +68 -0
- package/dist/components/workflow-run-list/workflow-run-filter-menu.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-filters.d.ts +18 -0
- package/dist/components/workflow-run-list/workflow-run-filters.d.ts.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-filters.js +236 -0
- package/dist/components/workflow-run-list/workflow-run-filters.js.map +1 -0
- package/dist/components/workflow-run-list/workflow-run-list-content.d.ts +8 -4
- package/dist/components/workflow-run-list/workflow-run-list-content.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-content.js +40 -12
- package/dist/components/workflow-run-list/workflow-run-list-content.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-states.d.ts +29 -2
- package/dist/components/workflow-run-list/workflow-run-list-states.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-states.js +103 -44
- package/dist/components/workflow-run-list/workflow-run-list-states.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.d.ts +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list-view.js +60 -43
- package/dist/components/workflow-run-list/workflow-run-list-view.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list.d.ts +1 -1
- package/dist/components/workflow-run-list/workflow-run-list.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-list.js +20 -8
- package/dist/components/workflow-run-list/workflow-run-list.js.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.d.ts +19 -9
- package/dist/components/workflow-run-list/workflow-run-row.d.ts.map +1 -1
- package/dist/components/workflow-run-list/workflow-run-row.js +179 -112
- package/dist/components/workflow-run-list/workflow-run-row.js.map +1 -1
- package/dist/components/workflow-run-number-label.d.ts +15 -0
- package/dist/components/workflow-run-number-label.d.ts.map +1 -0
- package/dist/components/workflow-run-number-label.js +41 -0
- package/dist/components/workflow-run-number-label.js.map +1 -0
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts +3 -3
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.d.ts.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js +10 -9
- package/dist/components/workflow-run-summary/workflow-run-attempt-switcher.js.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts +4 -9
- package/dist/components/workflow-run-summary/workflow-run-summary.d.ts.map +1 -1
- package/dist/components/workflow-run-summary/workflow-run-summary.js +107 -74
- package/dist/components/workflow-run-summary/workflow-run-summary.js.map +1 -1
- package/dist/components/workflow-run-tabs/index.d.ts +6 -0
- package/dist/components/workflow-run-tabs/index.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/index.js +7 -0
- package/dist/components/workflow-run-tabs/index.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts +11 -0
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.js +71 -0
- package/dist/components/workflow-run-tabs/run-annotation-summary-line.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts +2 -0
- package/dist/components/workflow-run-tabs/run-annotations-empty.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-annotations-empty.js +11 -0
- package/dist/components/workflow-run-tabs/run-annotations-empty.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-jobs-list.d.ts +18 -0
- package/dist/components/workflow-run-tabs/run-jobs-list.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-jobs-list.js +107 -0
- package/dist/components/workflow-run-tabs/run-jobs-list.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-tab-count.d.ts +6 -0
- package/dist/components/workflow-run-tabs/run-tab-count.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-tab-count.js +27 -0
- package/dist/components/workflow-run-tabs/run-tab-count.js.map +1 -0
- package/dist/components/workflow-run-tabs/run-tab-strip.d.ts +8 -0
- package/dist/components/workflow-run-tabs/run-tab-strip.d.ts.map +1 -0
- package/dist/components/workflow-run-tabs/run-tab-strip.js +82 -0
- package/dist/components/workflow-run-tabs/run-tab-strip.js.map +1 -0
- package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts +2 -2
- package/dist/components/workflow-run-view/agent-config-failure-callout.d.ts.map +1 -1
- package/dist/components/workflow-run-view/agent-config-failure-callout.js +4 -4
- package/dist/components/workflow-run-view/agent-config-failure-callout.js.map +1 -1
- package/dist/components/workflow-run-view/job-card.d.ts +2 -2
- package/dist/components/workflow-run-view/job-card.d.ts.map +1 -1
- package/dist/components/workflow-run-view/job-card.js +51 -32
- package/dist/components/workflow-run-view/job-card.js.map +1 -1
- package/dist/components/workflow-run-view/job-execution-switcher.d.ts +1 -1
- package/dist/components/workflow-run-view/job-execution-switcher.d.ts.map +1 -1
- package/dist/components/workflow-run-view/job-execution-switcher.js +20 -8
- package/dist/components/workflow-run-view/job-execution-switcher.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-selection.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-selection.js +40 -1
- package/dist/components/workflow-run-view/workflow-run-selection.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-states.d.ts +1 -0
- package/dist/components/workflow-run-view/workflow-run-states.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-states.js +10 -1
- package/dist/components/workflow-run-view/workflow-run-states.js.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.d.ts +10 -5
- package/dist/components/workflow-run-view/workflow-run-view.d.ts.map +1 -1
- package/dist/components/workflow-run-view/workflow-run-view.js +136 -85
- package/dist/components/workflow-run-view/workflow-run-view.js.map +1 -1
- package/dist/components/workflow-source-panel/index.d.ts +1 -0
- package/dist/components/workflow-source-panel/index.d.ts.map +1 -1
- package/dist/components/workflow-source-panel/index.js +1 -0
- package/dist/components/workflow-source-panel/index.js.map +1 -1
- package/dist/components/workflow-source-panel/workflow-source-content.d.ts +12 -0
- package/dist/components/workflow-source-panel/workflow-source-content.d.ts.map +1 -0
- package/dist/components/workflow-source-panel/workflow-source-content.js +53 -0
- package/dist/components/workflow-source-panel/workflow-source-content.js.map +1 -0
- package/dist/components/workflow-source-panel/workflow-source-panel.d.ts +1 -1
- package/dist/components/workflow-source-panel/workflow-source-panel.d.ts.map +1 -1
- package/dist/components/workflow-source-panel/workflow-source-panel.js +7 -54
- package/dist/components/workflow-source-panel/workflow-source-panel.js.map +1 -1
- package/dist/components/workflow-status/status-visuals.d.ts +4 -4
- package/dist/components/workflow-status/status-visuals.d.ts.map +1 -1
- package/dist/components/workflow-status/status-visuals.js +7 -0
- package/dist/components/workflow-status/status-visuals.js.map +1 -1
- package/dist/components/workflow-status/workflow-status-icon.d.ts +3 -4
- package/dist/components/workflow-status/workflow-status-icon.d.ts.map +1 -1
- package/dist/components/workflow-status/workflow-status-icon.js +6 -4
- package/dist/components/workflow-status/workflow-status-icon.js.map +1 -1
- package/dist/core/entities/job-execution.d.ts +10 -0
- package/dist/core/entities/job-execution.d.ts.map +1 -1
- package/dist/core/entities/job-execution.js +11 -0
- package/dist/core/entities/job-execution.js.map +1 -1
- package/dist/core/entities/job.d.ts +4 -2
- package/dist/core/entities/job.d.ts.map +1 -1
- package/dist/core/entities/job.js +8 -1
- package/dist/core/entities/job.js.map +1 -1
- package/dist/core/entities/step.d.ts +1 -1
- package/dist/core/entities/step.d.ts.map +1 -1
- package/dist/core/entities/step.js +2 -0
- package/dist/core/entities/step.js.map +1 -1
- package/dist/core/entities/workflow-run.d.ts +52 -4
- package/dist/core/entities/workflow-run.d.ts.map +1 -1
- package/dist/core/entities/workflow-run.js +22 -3
- package/dist/core/entities/workflow-run.js.map +1 -1
- package/dist/core/workflow-run-tabs.d.ts +9 -0
- package/dist/core/workflow-run-tabs.d.ts.map +1 -0
- package/dist/core/workflow-run-tabs.js +3 -0
- package/dist/core/workflow-run-tabs.js.map +1 -0
- package/dist/core/workflow-run.d.ts +6 -6
- package/dist/core/workflow-run.d.ts.map +1 -1
- package/dist/core/workflow-run.js +3 -3
- package/dist/core/workflow-run.js.map +1 -1
- package/dist/feature.d.ts +7 -7
- package/dist/feature.js +5 -5
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/workflow-run-mapper.d.ts +4 -3
- package/dist/hooks/api/workflow-run-mapper.d.ts.map +1 -1
- package/dist/hooks/api/workflow-run-mapper.js +31 -3
- package/dist/hooks/api/workflow-run-mapper.js.map +1 -1
- package/dist/hooks/api/workflow-runs.d.ts +4 -4
- package/dist/hooks/api/workflow-runs.d.ts.map +1 -1
- package/dist/hooks/api/workflow-runs.js +12 -4
- package/dist/hooks/api/workflow-runs.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/pages/project-workflows-page.js +46 -1
- package/dist/pages/project-workflows-page.js.map +1 -1
- package/dist/pages/workflow-run-detail-page.d.ts +11 -0
- package/dist/pages/workflow-run-detail-page.d.ts.map +1 -0
- package/dist/pages/workflow-run-detail-page.js +52 -0
- package/dist/pages/workflow-run-detail-page.js.map +1 -0
- package/dist/pages/workflow-run-list-page.d.ts +10 -0
- package/dist/pages/workflow-run-list-page.d.ts.map +1 -0
- package/dist/pages/workflow-run-list-page.js +43 -0
- package/dist/pages/workflow-run-list-page.js.map +1 -0
- package/dist/routes/inputs.d.ts +94 -5
- package/dist/routes/inputs.d.ts.map +1 -1
- package/dist/routes/inputs.js +199 -13
- package/dist/routes/inputs.js.map +1 -1
- package/dist/routes/project-route.d.ts +6 -0
- package/dist/routes/project-route.d.ts.map +1 -0
- package/dist/routes/project-route.js +25 -0
- package/dist/routes/project-route.js.map +1 -0
- package/dist/routes/run-detail.d.ts.map +1 -1
- package/dist/routes/run-detail.js +12 -7
- package/dist/routes/run-detail.js.map +1 -1
- package/dist/routes/runs.d.ts.map +1 -1
- package/dist/routes/runs.js +11 -6
- package/dist/routes/runs.js.map +1 -1
- package/dist/routes/workflows.d.ts.map +1 -1
- package/dist/routes/workflows.js +6 -5
- package/dist/routes/workflows.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/components/job-graph/job-duration-format.ts +11 -2
- package/src/components/job-graph/job-duration-label.tsx +5 -1
- package/src/components/job-graph/job-graph-content.tsx +7 -4
- package/src/components/job-graph/job-graph-view.test.tsx +19 -8
- package/src/components/job-graph/job-graph-view.tsx +6 -3
- package/src/components/job-graph/job-graph.stories.tsx +24 -20
- package/src/components/job-graph/job-node.stories.tsx +11 -6
- package/src/components/job-graph/job-node.test.tsx +82 -12
- package/src/components/job-graph/job-node.tsx +22 -15
- package/src/components/job-graph/types.ts +5 -1
- package/src/components/step-list/step-list.stories.tsx +2 -3
- package/src/components/step-list/step-list.tsx +3 -2
- package/src/components/workflow-run-duration-label.tsx +1 -1
- package/src/components/workflow-run-list/job-status-strip.tsx +189 -0
- package/src/components/workflow-run-list/run-display.test.ts +169 -30
- package/src/components/workflow-run-list/run-display.ts +116 -9
- package/src/components/workflow-run-list/types.ts +21 -17
- package/src/components/workflow-run-list/workflow-run-filter-menu.tsx +100 -0
- package/src/components/workflow-run-list/workflow-run-filters.tsx +243 -0
- package/src/components/workflow-run-list/workflow-run-list-content.tsx +44 -16
- package/src/components/workflow-run-list/workflow-run-list-states.tsx +128 -27
- package/src/components/workflow-run-list/workflow-run-list-view.test.tsx +441 -104
- package/src/components/workflow-run-list/workflow-run-list-view.tsx +73 -43
- package/src/components/workflow-run-list/workflow-run-list.stories.tsx +140 -20
- package/src/components/workflow-run-list/workflow-run-list.tsx +20 -10
- package/src/components/workflow-run-list/workflow-run-row.tsx +173 -106
- package/src/components/workflow-run-number-label.test.tsx +45 -0
- package/src/components/workflow-run-number-label.tsx +47 -0
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.test.tsx +7 -8
- package/src/components/workflow-run-summary/workflow-run-attempt-switcher.tsx +15 -14
- package/src/components/workflow-run-summary/workflow-run-summary.stories.tsx +29 -38
- package/src/components/workflow-run-summary/workflow-run-summary.test.tsx +30 -24
- package/src/components/workflow-run-summary/workflow-run-summary.tsx +102 -77
- package/src/components/workflow-run-tabs/index.ts +5 -0
- package/src/components/workflow-run-tabs/run-annotation-summary-line.test.tsx +69 -0
- package/src/components/workflow-run-tabs/run-annotation-summary-line.tsx +97 -0
- package/src/components/workflow-run-tabs/run-annotations-empty.tsx +11 -0
- package/src/components/workflow-run-tabs/run-jobs-list.tsx +151 -0
- package/src/components/workflow-run-tabs/run-tab-count.tsx +28 -0
- package/src/components/workflow-run-tabs/run-tab-strip.stories.tsx +52 -0
- package/src/components/workflow-run-tabs/run-tab-strip.test.tsx +117 -0
- package/src/components/workflow-run-tabs/run-tab-strip.tsx +97 -0
- package/src/components/workflow-run-view/agent-config-failure-callout.stories.tsx +2 -3
- package/src/components/workflow-run-view/agent-config-failure-callout.tsx +4 -4
- package/src/components/workflow-run-view/job-card.stories.tsx +11 -10
- package/src/components/workflow-run-view/job-card.test.tsx +115 -0
- package/src/components/workflow-run-view/job-card.tsx +74 -35
- package/src/components/workflow-run-view/job-execution-switcher.tsx +31 -9
- package/src/components/workflow-run-view/workflow-run-selection.test.ts +48 -0
- package/src/components/workflow-run-view/workflow-run-selection.ts +48 -1
- package/src/components/workflow-run-view/workflow-run-states.tsx +12 -1
- package/src/components/workflow-run-view/workflow-run-view.test.tsx +152 -94
- package/src/components/workflow-run-view/workflow-run-view.tsx +219 -95
- package/src/components/workflow-source-panel/index.ts +1 -0
- package/src/components/workflow-source-panel/workflow-source-content.tsx +84 -0
- package/src/components/workflow-source-panel/workflow-source-panel.tsx +14 -81
- package/src/components/workflow-status/status-visuals.test.ts +9 -0
- package/src/components/workflow-status/status-visuals.ts +6 -4
- package/src/components/workflow-status/workflow-status-icon.stories.tsx +10 -2
- package/src/components/workflow-status/workflow-status-icon.test.tsx +18 -0
- package/src/components/workflow-status/workflow-status-icon.tsx +17 -24
- package/src/core/entities/job-execution.ts +22 -0
- package/src/core/entities/job-status.test.ts +104 -0
- package/src/core/entities/job.ts +18 -3
- package/src/core/entities/step.ts +4 -0
- package/src/core/entities/workflow-run.ts +76 -7
- package/src/core/workflow-run-tabs.ts +8 -0
- package/src/core/workflow-run.test.ts +24 -9
- package/src/core/workflow-run.ts +17 -2
- package/src/feature.ts +5 -5
- package/src/hooks/api/workflow-run-mapper.ts +30 -3
- package/src/hooks/api/workflow-runs.test.tsx +2 -0
- package/src/hooks/api/workflow-runs.ts +12 -7
- package/src/index.ts +7 -2
- package/src/page-harness-budget.test.ts +1 -1
- package/src/pages/project-workflows-page.test.tsx +59 -5
- package/src/pages/project-workflows-page.tsx +34 -1
- package/src/pages/workflow-run-detail-page.tsx +72 -0
- package/src/pages/workflow-run-list-page.tsx +65 -0
- package/src/pages/workflow-run-pages.test.tsx +823 -0
- package/src/routes/inputs.test.ts +240 -12
- package/src/routes/inputs.ts +215 -11
- package/src/routes/project-route.tsx +25 -0
- package/src/routes/run-detail.tsx +15 -8
- package/src/routes/runs.tsx +14 -7
- package/src/routes/workflows.tsx +5 -4
- package/src/storybook-config.test.ts +27 -0
- package/test/fixtures/workflow-run.ts +76 -7
- package/test/pages.tsx +18 -8
- package/test/render.tsx +8 -2
- package/tsconfig.build.tsbuildinfo +1 -1
- package/tsconfig.test.json +7 -1
- package/dist/components/workflow-run-list/workflow-run-list-header.d.ts +0 -10
- package/dist/components/workflow-run-list/workflow-run-list-header.d.ts.map +0 -1
- package/dist/components/workflow-run-list/workflow-run-list-header.js +0 -55
- package/dist/components/workflow-run-list/workflow-run-list-header.js.map +0 -1
- package/dist/pages/workflow-run-first-time-use.d.ts +0 -6
- package/dist/pages/workflow-run-first-time-use.d.ts.map +0 -1
- package/dist/pages/workflow-run-first-time-use.js +0 -17
- package/dist/pages/workflow-run-first-time-use.js.map +0 -1
- package/dist/pages/workflow-run-page.d.ts +0 -10
- package/dist/pages/workflow-run-page.d.ts.map +0 -1
- package/dist/pages/workflow-run-page.js +0 -90
- package/dist/pages/workflow-run-page.js.map +0 -1
- package/src/components/workflow-run-list/workflow-run-list-header.tsx +0 -53
- package/src/pages/workflow-run-first-time-use.tsx +0 -17
- package/src/pages/workflow-run-page.test.tsx +0 -519
- package/src/pages/workflow-run-page.tsx +0 -94
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {JobStatusDto} from '@shipfox/api-workflows-dto';
|
|
2
|
+
import {screen, within} from '@testing-library/react';
|
|
2
3
|
import userEvent from '@testing-library/user-event';
|
|
3
4
|
import type {WorkflowRunListItem, WorkflowRunStatus} from '#core/workflow-run.js';
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
workflowRunJobsFixture,
|
|
7
|
+
workflowRunJobsOfStatus,
|
|
8
|
+
workflowRunListItem,
|
|
9
|
+
} from '#test/fixtures/workflow-run.js';
|
|
5
10
|
import {renderWithRouter} from '#test/render.js';
|
|
6
|
-
import type {WorkflowRunListQuery} from './types.js';
|
|
11
|
+
import type {WorkflowRunListQuery, WorkflowRunListViewProps} from './types.js';
|
|
7
12
|
import {WorkflowRunListView} from './workflow-run-list-view.js';
|
|
8
13
|
|
|
9
|
-
const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
|
|
10
14
|
const PROJECT_ID = '44444444-4444-4444-8444-444444444444';
|
|
15
|
+
const JOB_STRIP_LABEL_RE = /jobs:/u;
|
|
16
|
+
const JOB_SUMMARY_NAME_RE = /queued-build/u;
|
|
17
|
+
const WORKSPACE_SLUG = 'acme';
|
|
18
|
+
const PROJECT_SLUG = 'checkout-api';
|
|
11
19
|
|
|
12
|
-
function loadedQuery(): WorkflowRunListQuery {
|
|
20
|
+
function loadedQuery(overrides: Partial<WorkflowRunListQuery> = {}): WorkflowRunListQuery {
|
|
13
21
|
return {
|
|
14
22
|
isPending: false,
|
|
15
23
|
isError: false,
|
|
@@ -17,6 +25,7 @@ function loadedQuery(): WorkflowRunListQuery {
|
|
|
17
25
|
data: {pages: [], pageParams: []},
|
|
18
26
|
error: null,
|
|
19
27
|
refetch: () => undefined,
|
|
28
|
+
...overrides,
|
|
20
29
|
};
|
|
21
30
|
}
|
|
22
31
|
|
|
@@ -26,136 +35,464 @@ describe('WorkflowRunListView', () => {
|
|
|
26
35
|
vi.restoreAllMocks();
|
|
27
36
|
});
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
describe('filtering', () => {
|
|
39
|
+
test('narrows the list to the selected status', async () => {
|
|
40
|
+
const user = userEvent.setup();
|
|
41
|
+
renderListView([
|
|
42
|
+
run('running', 'deploy-web'),
|
|
43
|
+
run('failed', 'integration-tests'),
|
|
44
|
+
run('succeeded', 'build-image'),
|
|
45
|
+
]);
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
await selectFilterOption(user, 'Status', 'Failed');
|
|
38
48
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
});
|
|
49
|
+
expect(screen.getByText('integration-tests')).toBeInTheDocument();
|
|
50
|
+
expect(screen.queryByText('deploy-web')).not.toBeInTheDocument();
|
|
51
|
+
expect(screen.queryByText('build-image')).not.toBeInTheDocument();
|
|
52
|
+
});
|
|
44
53
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
test('keeps both statuses when two are selected', async () => {
|
|
55
|
+
const user = userEvent.setup();
|
|
56
|
+
renderListView([
|
|
57
|
+
run('running', 'deploy-web'),
|
|
58
|
+
run('failed', 'integration-tests'),
|
|
59
|
+
run('succeeded', 'build-image'),
|
|
60
|
+
]);
|
|
52
61
|
|
|
53
|
-
|
|
62
|
+
await selectFilterOption(user, 'Status', 'Failed');
|
|
63
|
+
await selectFilterOption(user, 'Status', 'Succeeded');
|
|
54
64
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
expect(screen.getByText('integration-tests')).toBeInTheDocument();
|
|
66
|
+
expect(screen.getByText('build-image')).toBeInTheDocument();
|
|
67
|
+
expect(screen.queryByText('deploy-web')).not.toBeInTheDocument();
|
|
68
|
+
});
|
|
59
69
|
|
|
60
|
-
|
|
61
|
-
|
|
70
|
+
test('the running status keeps in-progress pending runs alongside running ones', async () => {
|
|
71
|
+
const user = userEvent.setup();
|
|
72
|
+
renderListView([
|
|
73
|
+
run('running', 'deploy-web'),
|
|
74
|
+
run('pending', 'queued-build'),
|
|
75
|
+
run('succeeded', 'build-image'),
|
|
76
|
+
]);
|
|
62
77
|
|
|
63
|
-
|
|
64
|
-
// not yet navigable (its detail page does not exist until the canonical row replaces it).
|
|
65
|
-
const links = await screen.findAllByRole('link');
|
|
66
|
-
expect(links.some((link) => link.textContent?.includes('deploy-web'))).toBe(true);
|
|
67
|
-
expect(links.some((link) => link.textContent?.includes('queued-build'))).toBe(false);
|
|
68
|
-
expect(screen.getByText('queued-build')).toBeInTheDocument();
|
|
69
|
-
});
|
|
78
|
+
await selectFilterOption(user, 'Status', 'Running');
|
|
70
79
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
expect(screen.getByText('deploy-web')).toBeInTheDocument();
|
|
81
|
+
expect(screen.getByText('queued-build')).toBeInTheDocument();
|
|
82
|
+
expect(screen.queryByText('build-image')).not.toBeInTheDocument();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('narrows the list by branch, using options read off the loaded runs', async () => {
|
|
86
|
+
const user = userEvent.setup();
|
|
87
|
+
renderListView([
|
|
88
|
+
run('succeeded', 'deploy-web', 'run-1', {
|
|
89
|
+
trigger_reference: reference({ref: 'refs/heads/main'}),
|
|
90
|
+
}),
|
|
91
|
+
run('succeeded', 'deploy-api', 'run-2', {
|
|
92
|
+
trigger_reference: reference({ref: 'refs/heads/release/v2'}),
|
|
93
|
+
}),
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
await selectFilterOption(user, 'Branch', 'release/v2');
|
|
97
|
+
|
|
98
|
+
expect(screen.getByText('deploy-api')).toBeInTheDocument();
|
|
99
|
+
expect(screen.queryByText('deploy-web')).not.toBeInTheDocument();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('narrows the list by actor', async () => {
|
|
103
|
+
const user = userEvent.setup();
|
|
104
|
+
renderListView([
|
|
105
|
+
run('succeeded', 'deploy-web', 'run-1', {trigger_reference: reference({actor: 'octocat'})}),
|
|
106
|
+
run('succeeded', 'deploy-api', 'run-2', {trigger_reference: reference({actor: 'hubot'})}),
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
await selectFilterOption(user, 'Actor', 'hubot');
|
|
110
|
+
|
|
111
|
+
expect(screen.getByText('deploy-api')).toBeInTheDocument();
|
|
112
|
+
expect(screen.queryByText('deploy-web')).not.toBeInTheDocument();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('narrows the list by trigger event', async () => {
|
|
116
|
+
const user = userEvent.setup();
|
|
117
|
+
renderListView([
|
|
118
|
+
run('succeeded', 'deploy-web', 'run-1', {trigger_event: 'push'}),
|
|
119
|
+
run('succeeded', 'deploy-api', 'run-2', {trigger_event: 'pull_request'}),
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
await selectFilterOption(user, 'Event', 'pull_request');
|
|
123
|
+
|
|
124
|
+
expect(screen.getByText('deploy-api')).toBeInTheDocument();
|
|
125
|
+
expect(screen.queryByText('deploy-web')).not.toBeInTheDocument();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('names the selected value on the trigger instead of only a count', async () => {
|
|
129
|
+
const user = userEvent.setup();
|
|
130
|
+
renderListView([run('failed', 'integration-tests')]);
|
|
131
|
+
|
|
132
|
+
await selectFilterOption(user, 'Status', 'Failed');
|
|
133
|
+
|
|
134
|
+
const trigger = screen.getByRole('button', {name: filterTrigger('Status')});
|
|
135
|
+
expect(trigger).toHaveTextContent('Status: Failed');
|
|
136
|
+
// The value is in the accessible name too, not just the visible label.
|
|
137
|
+
expect(trigger).toHaveAccessibleName('Status: Failed filter');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('restores every row after the filters are cleared', async () => {
|
|
141
|
+
const user = userEvent.setup();
|
|
142
|
+
renderListView([
|
|
143
|
+
run('running', 'deploy-web'),
|
|
144
|
+
run('failed', 'integration-tests'),
|
|
145
|
+
run('succeeded', 'build-image'),
|
|
146
|
+
]);
|
|
147
|
+
await user.type(await screen.findByLabelText('Search runs'), 'no-such-run');
|
|
148
|
+
|
|
149
|
+
expect(screen.getByText('No matching runs')).toBeInTheDocument();
|
|
150
|
+
|
|
151
|
+
await user.click(screen.getByRole('button', {name: 'Clear filters'}));
|
|
88
152
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
expect(duration).toHaveAttribute('aria-label', 'running 2m 14s');
|
|
102
|
-
expect(
|
|
103
|
-
screen.getByRole('link', {
|
|
104
|
-
name: (name) => name.includes('deploy-web') && name.includes('running 2m 14s'),
|
|
105
|
-
}),
|
|
106
|
-
).toBeInTheDocument();
|
|
153
|
+
expect(screen.getByText('deploy-web')).toBeInTheDocument();
|
|
154
|
+
expect(screen.getByText('integration-tests')).toBeInTheDocument();
|
|
155
|
+
expect(screen.getByText('build-image')).toBeInTheDocument();
|
|
156
|
+
expect(screen.getByLabelText('Search runs')).toHaveValue('');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('offers no way to clear filters while none are active', async () => {
|
|
160
|
+
renderListView([run('succeeded', 'build-image')]);
|
|
161
|
+
|
|
162
|
+
expect(await screen.findByText('build-image')).toBeInTheDocument();
|
|
163
|
+
expect(screen.queryByRole('button', {name: 'Clear filters'})).not.toBeInTheDocument();
|
|
164
|
+
});
|
|
107
165
|
});
|
|
108
166
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
167
|
+
describe('interaction states', () => {
|
|
168
|
+
test('renders skeleton rows while the first page loads', async () => {
|
|
169
|
+
renderListView([], {query: loadedQuery({isPending: true, data: undefined})});
|
|
170
|
+
|
|
171
|
+
expect(await screen.findByLabelText('Loading runs')).toBeInTheDocument();
|
|
172
|
+
expect(screen.queryByText('No runs yet')).not.toBeInTheDocument();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('keeps the filter row usable while loading', async () => {
|
|
176
|
+
renderListView([], {query: loadedQuery({isPending: true, data: undefined})});
|
|
177
|
+
|
|
178
|
+
expect(await screen.findByLabelText('Search runs')).toBeInTheDocument();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('offers the onboarding call to action when a project has no runs at all', async () => {
|
|
182
|
+
renderListView([]);
|
|
183
|
+
|
|
184
|
+
expect(await screen.findByText('No runs yet')).toBeInTheDocument();
|
|
185
|
+
expect(screen.getByRole('link', {name: 'View workflows'})).toBeInTheDocument();
|
|
186
|
+
expect(screen.queryByRole('button', {name: 'Show all runs'})).not.toBeInTheDocument();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('offers a way out rather than onboarding when filters emptied the list', async () => {
|
|
190
|
+
const user = userEvent.setup();
|
|
191
|
+
renderListView([run('succeeded', 'build-image')]);
|
|
192
|
+
|
|
193
|
+
await user.type(await screen.findByLabelText('Search runs'), 'no-such-run');
|
|
194
|
+
|
|
195
|
+
expect(screen.getByText('No matching runs')).toBeInTheDocument();
|
|
196
|
+
expect(screen.queryByText('No runs yet')).not.toBeInTheDocument();
|
|
197
|
+
expect(screen.getByRole('button', {name: 'Show all runs'})).toBeInTheDocument();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('reports a filtered empty list as no matches even when nothing loaded', async () => {
|
|
201
|
+
const user = userEvent.setup();
|
|
202
|
+
renderListView([run('succeeded', 'build-image')]);
|
|
203
|
+
|
|
204
|
+
await selectFilterOption(user, 'Status', 'Cancelled');
|
|
205
|
+
|
|
206
|
+
expect(screen.getByText('No matching runs')).toBeInTheDocument();
|
|
207
|
+
expect(screen.queryByText('No runs yet')).not.toBeInTheDocument();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('renders a failed first fetch as an error, never as an empty list', async () => {
|
|
211
|
+
renderListView([], {
|
|
212
|
+
query: loadedQuery({isError: true, data: undefined, error: new Error('boom')}),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(await screen.findByLabelText('Search runs')).toBeInTheDocument();
|
|
216
|
+
expect(screen.queryByText('No runs yet')).not.toBeInTheDocument();
|
|
217
|
+
expect(screen.queryByText('No matching runs')).not.toBeInTheDocument();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('still reports no matches under a stale-refresh banner', async () => {
|
|
221
|
+
const user = userEvent.setup();
|
|
222
|
+
renderListView([run('succeeded', 'build-image')], {
|
|
223
|
+
query: loadedQuery({isError: true, error: new Error('boom')}),
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
await user.type(await screen.findByLabelText('Search runs'), 'no-such-run');
|
|
227
|
+
|
|
228
|
+
expect(screen.getByText('Could not refresh workflow runs.')).toBeInTheDocument();
|
|
229
|
+
expect(screen.getByText('No matching runs')).toBeInTheDocument();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('keeps loaded rows and the filters behind a retry when a refetch fails', async () => {
|
|
233
|
+
const user = userEvent.setup();
|
|
234
|
+
const refetch = vi.fn();
|
|
235
|
+
renderListView([run('succeeded', 'build-image')], {
|
|
236
|
+
query: loadedQuery({isError: true, error: new Error('boom'), refetch}),
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
expect(await screen.findByText('build-image')).toBeInTheDocument();
|
|
240
|
+
expect(screen.getByText('Could not refresh workflow runs.')).toBeInTheDocument();
|
|
112
241
|
|
|
113
|
-
|
|
114
|
-
await screen.findByRole('link', {
|
|
115
|
-
name: (name) =>
|
|
116
|
-
name.includes('integration-tests') && name.includes('github') && name.includes('push'),
|
|
117
|
-
}),
|
|
118
|
-
);
|
|
242
|
+
await user.click(screen.getByRole('button', {name: 'Retry'}));
|
|
119
243
|
|
|
120
|
-
|
|
244
|
+
expect(refetch).toHaveBeenCalledOnce();
|
|
245
|
+
});
|
|
121
246
|
|
|
122
|
-
|
|
247
|
+
test('offers an explicit load more when more pages exist', async () => {
|
|
248
|
+
const user = userEvent.setup();
|
|
249
|
+
const onLoadMore = vi.fn();
|
|
250
|
+
renderListView([run('succeeded', 'recent-run')], {hasNextPage: true, onLoadMore});
|
|
123
251
|
|
|
124
|
-
|
|
252
|
+
await user.click(await screen.findByRole('button', {name: 'Load more runs'}));
|
|
253
|
+
|
|
254
|
+
expect(onLoadMore).toHaveBeenCalledOnce();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('offers older pages before reporting that a filter has no matches', async () => {
|
|
258
|
+
const user = userEvent.setup();
|
|
259
|
+
const onLoadMore = vi.fn();
|
|
260
|
+
renderListView([run('succeeded', 'recent-run')], {hasNextPage: true, onLoadMore});
|
|
261
|
+
|
|
262
|
+
await user.type(await screen.findByLabelText('Search runs'), 'older-run');
|
|
263
|
+
|
|
264
|
+
expect(screen.getByText('No matches in loaded history')).toBeInTheDocument();
|
|
265
|
+
await user.click(screen.getByRole('button', {name: 'Load more runs'}));
|
|
266
|
+
expect(onLoadMore).toHaveBeenCalledOnce();
|
|
267
|
+
});
|
|
125
268
|
});
|
|
126
269
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
270
|
+
describe('row anatomy', () => {
|
|
271
|
+
test('renders the branch, commit, and actor a source-control trigger carries', async () => {
|
|
272
|
+
renderListView([
|
|
273
|
+
run('succeeded', 'deploy-web', 'run-1', {
|
|
274
|
+
trigger_reference: {
|
|
275
|
+
repository: 'acme/api',
|
|
276
|
+
ref: 'refs/heads/release/v2',
|
|
277
|
+
commit: 'abcdef1234567890',
|
|
278
|
+
actor: 'octocat',
|
|
279
|
+
},
|
|
280
|
+
}),
|
|
281
|
+
]);
|
|
282
|
+
|
|
283
|
+
expect(await screen.findByText('release/v2')).toBeInTheDocument();
|
|
284
|
+
expect(screen.getByText('abcdef1')).toBeInTheDocument();
|
|
285
|
+
expect(screen.getByText('octocat')).toBeInTheDocument();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('shows a pull request ref by number rather than as a raw ref', async () => {
|
|
289
|
+
renderListView([
|
|
290
|
+
run('succeeded', 'deploy-web', 'run-1', {
|
|
291
|
+
trigger_reference: reference({ref: 'refs/pull/42/head'}),
|
|
292
|
+
}),
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
expect(await screen.findByText('#42')).toBeInTheDocument();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('summarizes the job strip as one accessible name rather than one per glyph', async () => {
|
|
299
|
+
renderListView([
|
|
300
|
+
run('failed', 'deploy-web', 'run-1', {
|
|
301
|
+
...workflowRunJobsFixture(['succeeded', 'failed', 'pending']),
|
|
302
|
+
}),
|
|
303
|
+
]);
|
|
304
|
+
|
|
305
|
+
expect(
|
|
306
|
+
await screen.findByRole('img', {name: '3 jobs: 1 failed, 1 pending, 1 succeeded'}),
|
|
307
|
+
).toBeInTheDocument();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// The link's aria-label replaces its contents, so the strip's own label is not spoken
|
|
311
|
+
// when the row is announced as a link. Where a run failed has to survive that.
|
|
312
|
+
test('carries the job breakdown into the row link name', async () => {
|
|
313
|
+
renderListView([
|
|
314
|
+
run('failed', 'deploy-web', 'run-1', {
|
|
315
|
+
...workflowRunJobsFixture(['succeeded', 'failed', 'pending']),
|
|
316
|
+
}),
|
|
317
|
+
]);
|
|
318
|
+
|
|
319
|
+
expect(
|
|
320
|
+
await screen.findByRole('link', {
|
|
321
|
+
name: (name) => name.includes('3 jobs: 1 failed, 1 pending, 1 succeeded'),
|
|
322
|
+
}),
|
|
323
|
+
).toBeInTheDocument();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('leaves the job breakdown out of the link name when no jobs are planned', async () => {
|
|
327
|
+
renderListView([run('pending', 'queued-build', 'run-1', {...workflowRunJobsFixture([])})]);
|
|
328
|
+
|
|
329
|
+
const link = await screen.findByRole('link', {name: JOB_SUMMARY_NAME_RE});
|
|
330
|
+
expect(link.getAttribute('aria-label')).not.toContain('jobs:');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test('counts the jobs it had to hide beyond the strip threshold', async () => {
|
|
334
|
+
renderListView([run('running', 'deploy-web', 'run-1', {...workflowRunJobsOfStatus(40)})]);
|
|
335
|
+
|
|
336
|
+
expect(await screen.findByText('+33')).toBeInTheDocument();
|
|
337
|
+
expect(screen.getByRole('img', {name: '40 jobs: 40 succeeded'})).toBeInTheDocument();
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// The API sends a bounded preview, so a run whose only failure sits past it has no failed
|
|
341
|
+
// job in the payload at all. The counts still carry it, and the overflow glyph has to say
|
|
342
|
+
// so rather than showing an unbroken row of green.
|
|
343
|
+
test('reports a failure that sits beyond the fetched preview', async () => {
|
|
344
|
+
const statuses = [
|
|
345
|
+
...(Array.from({length: 20}, () => 'succeeded') as JobStatusDto[]),
|
|
346
|
+
'failed' as const,
|
|
347
|
+
];
|
|
348
|
+
renderListView([run('failed', 'deploy-web', 'run-1', {...workflowRunJobsFixture(statuses)})]);
|
|
349
|
+
|
|
350
|
+
const strip = await screen.findByRole('img', {
|
|
351
|
+
name: '21 jobs: 1 failed, 20 succeeded',
|
|
352
|
+
});
|
|
353
|
+
expect(strip).toBeInTheDocument();
|
|
354
|
+
expect(screen.getByText('+14')).toBeInTheDocument();
|
|
355
|
+
expect(within(strip).getByLabelText('Failed')).toBeInTheDocument();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Nothing caps a workflow's job count, so an exact overflow count is unbounded in width
|
|
359
|
+
// and would eventually paint over the duration column beside it.
|
|
360
|
+
test('abbreviates an overflow count too wide to print exactly', async () => {
|
|
361
|
+
renderListView([
|
|
362
|
+
run('running', 'huge', 'run-1', {
|
|
363
|
+
...workflowRunJobsOfStatus(12_000, 'pending'),
|
|
364
|
+
}),
|
|
365
|
+
]);
|
|
366
|
+
|
|
367
|
+
expect(await screen.findByText('+12K')).toBeInTheDocument();
|
|
368
|
+
// The exact figure survives where a precise number is actually read.
|
|
369
|
+
expect(screen.getByRole('img', {name: '12000 jobs: 12000 pending'})).toBeInTheDocument();
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test('renders no strip for a run whose jobs are not planned yet', async () => {
|
|
373
|
+
renderListView([run('pending', 'queued-build', 'run-1', {...workflowRunJobsFixture([])})]);
|
|
135
374
|
|
|
136
|
-
|
|
375
|
+
expect(await screen.findByText('queued-build')).toBeInTheDocument();
|
|
376
|
+
expect(screen.queryByRole('img', {name: JOB_STRIP_LABEL_RE})).not.toBeInTheDocument();
|
|
377
|
+
});
|
|
137
378
|
|
|
138
|
-
|
|
379
|
+
test('renders optimistic temp runs without a navigable link', async () => {
|
|
380
|
+
const optimisticRun = {
|
|
381
|
+
...run('pending', 'queued-build', 'temp-1234', {workflow_name: 'CI'}),
|
|
382
|
+
number: null,
|
|
383
|
+
};
|
|
384
|
+
renderListView([optimisticRun, run('running', 'deploy-web')]);
|
|
139
385
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
386
|
+
// The canonical run is a link to its detail page; the optimistic temp run is shown but
|
|
387
|
+
// not yet navigable (its detail page does not exist until the canonical row replaces it).
|
|
388
|
+
const links = await screen.findAllByRole('link');
|
|
389
|
+
expect(links.some((link) => link.textContent?.includes('deploy-web'))).toBe(true);
|
|
390
|
+
expect(links.some((link) => link.textContent?.includes('queued-build'))).toBe(false);
|
|
391
|
+
expect(screen.getByText('queued-build')).toBeInTheDocument();
|
|
392
|
+
expect(screen.queryByText('CI #1')).not.toBeInTheDocument();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test('shows a finished run duration in the row metadata', async () => {
|
|
396
|
+
renderListView([
|
|
397
|
+
run('succeeded', 'build-image', 'run-build-image', {
|
|
398
|
+
started_at: '2026-05-07T01:00:00.000Z',
|
|
399
|
+
finished_at: '2026-05-07T01:02:14.000Z',
|
|
400
|
+
}),
|
|
401
|
+
]);
|
|
402
|
+
|
|
403
|
+
const duration = await screen.findByText('2m 14s');
|
|
404
|
+
expect(duration).toHaveAttribute('aria-label', 'ran 2m 14s');
|
|
405
|
+
expect(
|
|
406
|
+
screen.getByRole('link', {
|
|
407
|
+
name: (name) => name.includes('build-image') && name.includes('ran 2m 14s'),
|
|
408
|
+
}),
|
|
409
|
+
).toBeInTheDocument();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test('shows a live running run duration in the row metadata', async () => {
|
|
413
|
+
vi.spyOn(Date, 'now').mockReturnValue(Date.parse('2026-05-07T01:02:14.000Z'));
|
|
414
|
+
|
|
415
|
+
renderListView([
|
|
416
|
+
run('running', 'deploy-web', 'run-deploy-web', {
|
|
417
|
+
started_at: '2026-05-07T01:00:00.000Z',
|
|
418
|
+
finished_at: null,
|
|
419
|
+
}),
|
|
420
|
+
]);
|
|
421
|
+
|
|
422
|
+
const duration = await screen.findByText('2m 14s');
|
|
423
|
+
expect(duration).toHaveAttribute('aria-label', 'running 2m 14s');
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test('renders the workflow name beside the run number', async () => {
|
|
427
|
+
renderListView([
|
|
428
|
+
run('succeeded', 'Deploy production', 'run-deploy-production', {
|
|
429
|
+
number: 5184,
|
|
430
|
+
workflow_name: 'CI',
|
|
431
|
+
}),
|
|
432
|
+
]);
|
|
433
|
+
|
|
434
|
+
expect(await screen.findByText('CI #5184')).toBeInTheDocument();
|
|
435
|
+
expect(
|
|
436
|
+
screen.getByRole('link', {name: (name) => name.includes('CI #5184')}),
|
|
437
|
+
).toBeInTheDocument();
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test('scopes the trigger tooltip to the trigger label', async () => {
|
|
441
|
+
const user = userEvent.setup();
|
|
442
|
+
renderListView([run('failed', 'integration-tests')]);
|
|
443
|
+
|
|
444
|
+
await user.hover(await screen.findByText('push'));
|
|
445
|
+
|
|
446
|
+
expect(await screen.findByRole('tooltip')).toHaveTextContent('github_acme · push');
|
|
447
|
+
});
|
|
144
448
|
});
|
|
145
449
|
});
|
|
146
450
|
|
|
147
|
-
function renderListView(
|
|
451
|
+
function renderListView(
|
|
452
|
+
runs: WorkflowRunListItem[],
|
|
453
|
+
{
|
|
454
|
+
query = loadedQuery(),
|
|
455
|
+
...options
|
|
456
|
+
}: Partial<Pick<WorkflowRunListViewProps, 'hasNextPage' | 'onLoadMore' | 'query'>> = {},
|
|
457
|
+
) {
|
|
148
458
|
// Row links need router context; the query and data stay injected by props.
|
|
149
459
|
renderWithRouter(
|
|
150
460
|
<WorkflowRunListView
|
|
151
461
|
runs={runs}
|
|
152
|
-
query={
|
|
153
|
-
|
|
154
|
-
|
|
462
|
+
query={query}
|
|
463
|
+
workspaceSlug={WORKSPACE_SLUG}
|
|
464
|
+
projectSlug={PROJECT_SLUG}
|
|
465
|
+
{...options}
|
|
155
466
|
/>,
|
|
156
467
|
);
|
|
157
468
|
}
|
|
158
469
|
|
|
470
|
+
/** Matches a filter trigger by its label, whatever value it currently reports. */
|
|
471
|
+
function filterTrigger(label: string): RegExp {
|
|
472
|
+
return new RegExp(`^${label}\\b.*filter$`, 'u');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function selectFilterOption(
|
|
476
|
+
user: ReturnType<typeof userEvent.setup>,
|
|
477
|
+
label: string,
|
|
478
|
+
option: string,
|
|
479
|
+
) {
|
|
480
|
+
await user.click(await screen.findByRole('button', {name: filterTrigger(label)}));
|
|
481
|
+
const menu = await screen.findByRole('menu');
|
|
482
|
+
await user.click(within(menu).getByRole('menuitemcheckbox', {name: option}));
|
|
483
|
+
await user.keyboard('{Escape}');
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function reference(overrides: Partial<NonNullable<WorkflowRunListItem['triggerReference']>> = {}) {
|
|
487
|
+
return {
|
|
488
|
+
repository: 'acme/api',
|
|
489
|
+
ref: 'refs/heads/main',
|
|
490
|
+
commit: 'abcdef1234567890',
|
|
491
|
+
actor: 'octocat',
|
|
492
|
+
...overrides,
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
159
496
|
function run(
|
|
160
497
|
status: WorkflowRunStatus,
|
|
161
498
|
name: string,
|