FN-5925: polish task review tab layout
Refine the task review tab layout for clearer hierarchy and better mobile behavior. - group the review summary and decision badge into a stacked header layout - reorganize review items with dedicated selection, status, and metadata regions - restyle review cards, status badges, auto-merge controls, and body containers for improved spacing and overflow handling - extend TaskReviewTab coverage for populated layout hooks and mobile CSS regressions Files changed: packages/dashboard/app/components/TaskReviewTab.css | 220 +++++++++++++++------ packages/dashboard/app/components/TaskReviewTab.tsx | 24 ++- packages/dashboard/app/components/__tests__/TaskReviewTab.test.tsx | 32 ++- 3 files changed, 206 insertions(+), 70 deletions(-) Fusion-Task-Id: FN-5925 Fusion-Task-Lineage: fdb9d882-abda-48b0-aed0-a1ff4262af8f
This commit is contained in:
@@ -1,110 +1,188 @@
|
|||||||
.task-review-tab {
|
.task-review-tab {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-md);
|
gap: var(--space-lg);
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__header {
|
.task-review-tab__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--space-md);
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
gap: var(--space-lg);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__summary-wrap {
|
.task-review-tab__summary-wrap {
|
||||||
|
flex: 1 1 20rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-review-tab__summary-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
gap: var(--space-sm);
|
align-items: flex-start;
|
||||||
flex-wrap: wrap;
|
gap: var(--space-xs);
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__summary {
|
.task-review-tab__summary {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--text-muted);
|
color: var(--text);
|
||||||
|
font-family: var(--font-primary);
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.5;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__decision {
|
.task-review-tab__decision,
|
||||||
|
.task-review-tab__status {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
inline-size: fit-content;
|
||||||
|
min-width: 0;
|
||||||
padding: 0 var(--space-sm);
|
padding: 0 var(--space-sm);
|
||||||
|
min-height: calc(var(--space-lg) + var(--space-xs));
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
|
border: var(--btn-border-width) solid transparent;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
line-height: 1.2;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__decision--APPROVED {
|
.task-review-tab__decision--APPROVED,
|
||||||
|
.task-review-tab__status--addressed {
|
||||||
color: var(--color-success);
|
color: var(--color-success);
|
||||||
|
background: color-mix(in srgb, var(--color-success) 14%, transparent);
|
||||||
|
border-color: color-mix(in srgb, var(--color-success) 28%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__decision--CHANGES_REQUESTED {
|
.task-review-tab__decision--CHANGES_REQUESTED,
|
||||||
|
.task-review-tab__status--failed {
|
||||||
color: var(--color-error);
|
color: var(--color-error);
|
||||||
|
background: color-mix(in srgb, var(--color-error) 14%, transparent);
|
||||||
|
border-color: color-mix(in srgb, var(--color-error) 28%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__decision--COMMENTED,
|
.task-review-tab__decision--COMMENTED,
|
||||||
.task-review-tab__decision--PENDING,
|
.task-review-tab__decision--PENDING,
|
||||||
.task-review-tab__decision--REVIEW_REQUIRED {
|
.task-review-tab__decision--REVIEW_REQUIRED,
|
||||||
|
.task-review-tab__status--queued,
|
||||||
|
.task-review-tab__status--in-progress {
|
||||||
color: var(--color-warning);
|
color: var(--color-warning);
|
||||||
|
background: color-mix(in srgb, var(--color-warning) 16%, transparent);
|
||||||
|
border-color: color-mix(in srgb, var(--color-warning) 30%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__actions {
|
.task-review-tab__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--space-sm);
|
flex-wrap: wrap;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
flex: 0 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-review-tab__actions .btn {
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__auto-merge-control {
|
.task-review-tab__auto-merge-control {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-2xs);
|
gap: var(--space-xs);
|
||||||
min-inline-size: max-content;
|
min-inline-size: 14rem;
|
||||||
|
padding: var(--space-sm);
|
||||||
|
border: var(--btn-border-width) solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: color-mix(in srgb, var(--surface) 72%, var(--card));
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__list {
|
.task-review-tab__list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-md);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__item {
|
.task-review-tab__item {
|
||||||
padding: var(--space-md);
|
padding: var(--card-padding);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__item-inner {
|
.task-review-tab__item-inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-md);
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__row {
|
.task-review-tab__direct-item {
|
||||||
display: grid;
|
display: block;
|
||||||
grid-template-columns: auto 1fr auto;
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-review-tab__direct-item--selectable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-review-tab__item-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
align-items: start;
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-review-tab__item-selection {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-review-tab__item-selection input {
|
||||||
|
margin: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__item-summary {
|
.task-review-tab__item-summary {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
|
font-family: var(--font-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.5;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__direct-item {
|
.task-review-tab__item-meta-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__body {
|
.task-review-tab__body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: var(--space-sm);
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: var(--space-md);
|
||||||
border: var(--btn-border-width) solid var(--border);
|
border: var(--btn-border-width) solid var(--border);
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-md);
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
|
font-family: var(--font-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.6;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
max-width: 100%;
|
overflow-wrap: anywhere;
|
||||||
font-family: var(--font-primary);
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__body.markdown-body {
|
.task-review-tab__body.markdown-body {
|
||||||
@@ -124,22 +202,12 @@
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__status {
|
.task-review-tab__meta,
|
||||||
color: var(--text-muted);
|
.task-review-tab__empty,
|
||||||
text-transform: capitalize;
|
.task-review-tab__error {
|
||||||
}
|
font-family: var(--font-primary);
|
||||||
|
font-size: 0.75rem;
|
||||||
.task-review-tab__status--failed {
|
line-height: 1.5;
|
||||||
color: var(--color-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-review-tab__status--queued,
|
|
||||||
.task-review-tab__status--in-progress {
|
|
||||||
color: var(--color-warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-review-tab__status--addressed {
|
|
||||||
color: var(--color-success);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__meta,
|
.task-review-tab__meta,
|
||||||
@@ -147,18 +215,35 @@
|
|||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__refresh-meta {
|
.task-review-tab__refresh-meta,
|
||||||
display: inline-flex;
|
.task-review-tab__empty,
|
||||||
align-items: center;
|
.task-review-tab__error {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
|
padding: var(--space-sm) var(--space-md);
|
||||||
|
border: var(--btn-border-width) solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: color-mix(in srgb, var(--surface) 72%, var(--card));
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-review-tab__refresh-meta {
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__refresh-meta .status-dot {
|
.task-review-tab__refresh-meta .status-dot {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
margin-top: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-review-tab__empty {
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__error {
|
.task-review-tab__error {
|
||||||
color: var(--color-error);
|
color: var(--color-error);
|
||||||
|
border-color: color-mix(in srgb, var(--color-error) 26%, var(--border));
|
||||||
|
background: color-mix(in srgb, var(--color-error) 12%, var(--surface));
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__reviewers,
|
.task-review-tab__reviewers,
|
||||||
@@ -173,27 +258,52 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.task-review-tab__row {
|
.task-review-tab {
|
||||||
grid-template-columns: auto 1fr;
|
gap: var(--space-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__status {
|
.task-review-tab__header {
|
||||||
grid-column: 2;
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-review-tab__summary-wrap,
|
||||||
|
.task-review-tab__actions,
|
||||||
|
.task-review-tab__auto-merge-control {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__actions {
|
.task-review-tab__actions {
|
||||||
width: 100%;
|
justify-content: flex-start;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__auto-merge-control {
|
.task-review-tab__actions .btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-inline-size: 0;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__auto-merge-control .select {
|
.task-review-tab__item,
|
||||||
width: 100%;
|
.task-review-tab__body,
|
||||||
|
.task-review-tab__refresh-meta,
|
||||||
|
.task-review-tab__empty,
|
||||||
|
.task-review-tab__error {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-review-tab__item-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-review-tab__status,
|
||||||
|
.task-review-tab__decision {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-review-tab__body {
|
||||||
|
padding: var(--space-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-review-tab__refresh-meta {
|
.task-review-tab__refresh-meta {
|
||||||
|
|||||||
@@ -314,8 +314,10 @@ export function TaskReviewTab({
|
|||||||
<div className="task-review-tab">
|
<div className="task-review-tab">
|
||||||
<div className="task-review-tab__header">
|
<div className="task-review-tab__header">
|
||||||
<div className="task-review-tab__summary-wrap">
|
<div className="task-review-tab__summary-wrap">
|
||||||
<p className="task-review-tab__summary">{summaryText}</p>
|
<div className="task-review-tab__summary-group">
|
||||||
{decisionLabel ? <span className={`task-review-tab__decision task-review-tab__decision--${decisionLabel}`}>{decisionLabel}</span> : null}
|
<p className="task-review-tab__summary">{summaryText}</p>
|
||||||
|
{decisionLabel ? <span className={`task-review-tab__decision task-review-tab__decision--${decisionLabel}`}>{decisionLabel}</span> : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="task-review-tab__actions">
|
<div className="task-review-tab__actions">
|
||||||
<div className="task-review-tab__auto-merge-control">
|
<div className="task-review-tab__auto-merge-control">
|
||||||
@@ -373,16 +375,20 @@ export function TaskReviewTab({
|
|||||||
<li key={item.id} className="task-review-tab__item card">
|
<li key={item.id} className="task-review-tab__item card">
|
||||||
<div className="task-review-tab__item-inner">
|
<div className="task-review-tab__item-inner">
|
||||||
<label htmlFor={checkboxId} className="task-review-tab__direct-item task-review-tab__direct-item--selectable">
|
<label htmlFor={checkboxId} className="task-review-tab__direct-item task-review-tab__direct-item--selectable">
|
||||||
<div className="task-review-tab__summary-wrap">
|
<div className="task-review-tab__item-header">
|
||||||
<input id={checkboxId} type="checkbox" checked={selected.includes(item.id)} onChange={() => toggleSelected(item.id)} />
|
<div className="task-review-tab__item-selection">
|
||||||
<span className="task-review-tab__item-summary">{item.path ? `${item.path}: ` : ""}{item.summary}</span>
|
<input id={checkboxId} type="checkbox" checked={selected.includes(item.id)} onChange={() => toggleSelected(item.id)} />
|
||||||
|
<span className="task-review-tab__item-summary">{item.path ? `${item.path}: ` : ""}{item.summary}</span>
|
||||||
|
</div>
|
||||||
<span className={`task-review-tab__status task-review-tab__status--${item.status}`}>{item.status}</span>
|
<span className={`task-review-tab__status task-review-tab__status--${item.status}`}>{item.status}</span>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<div className="task-review-tab__meta">{formatTimestamp(item.createdAt)}</div>
|
<div className="task-review-tab__item-meta-list">
|
||||||
{item.addressing ? (
|
<div className="task-review-tab__meta">{formatTimestamp(item.createdAt)}</div>
|
||||||
<div className="task-review-tab__meta">Selected: {formatTimestamp(item.addressing.selectedAt)}{item.addressing.startedAt ? ` · Started: ${formatTimestamp(item.addressing.startedAt)}` : ""}{item.addressing.completedAt ? ` · Completed: ${formatTimestamp(item.addressing.completedAt)}` : ""}{item.addressing.error ? ` · Error: ${item.addressing.error}` : ""}</div>
|
{item.addressing ? (
|
||||||
) : null}
|
<div className="task-review-tab__meta">Selected: {formatTimestamp(item.addressing.selectedAt)}{item.addressing.startedAt ? ` · Started: ${formatTimestamp(item.addressing.startedAt)}` : ""}{item.addressing.completedAt ? ` · Completed: ${formatTimestamp(item.addressing.completedAt)}` : ""}{item.addressing.error ? ` · Error: ${item.addressing.error}` : ""}</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
{renderMarkdown ? (
|
{renderMarkdown ? (
|
||||||
<div className="task-review-tab__body markdown-body">
|
<div className="task-review-tab__body markdown-body">
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ describe("TaskReviewTab", () => {
|
|||||||
expect(await screen.findByText("Failed to load review data.")).toBeInTheDocument();
|
expect(await screen.findByText("Failed to load review data.")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders PR decision and status modifiers", async () => {
|
it("renders PR decision, status modifiers, and populated layout hooks", async () => {
|
||||||
const task = makeTask({
|
const task = makeTask({
|
||||||
reviewState: {
|
reviewState: {
|
||||||
source: "pull-request",
|
source: "pull-request",
|
||||||
@@ -136,6 +136,8 @@ describe("TaskReviewTab", () => {
|
|||||||
body: "Fix null handling",
|
body: "Fix null handling",
|
||||||
author: { login: "reviewer" },
|
author: { login: "reviewer" },
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
|
path: "src/parser.ts",
|
||||||
|
summary: "Parser guard is missing",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
addressing: [{ itemId: "ri-1", status: "failed", selectedAt: new Date().toISOString() }],
|
addressing: [{ itemId: "ri-1", status: "failed", selectedAt: new Date().toISOString() }],
|
||||||
@@ -143,9 +145,19 @@ describe("TaskReviewTab", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
apiMocks.fetchTaskReview.mockResolvedValue({ reviewState: task.reviewState, automationStatus: null, emptyMessage: null });
|
apiMocks.fetchTaskReview.mockResolvedValue({ reviewState: task.reviewState, automationStatus: null, emptyMessage: null });
|
||||||
render(<TaskReviewTab task={task} addToast={vi.fn()} />);
|
const { container } = render(<TaskReviewTab task={task} addToast={vi.fn()} />);
|
||||||
|
|
||||||
await screen.findByText("CHANGES_REQUESTED");
|
await screen.findByText("CHANGES_REQUESTED");
|
||||||
expect(screen.getByText("failed").className).toContain("task-review-tab__status--failed");
|
expect(screen.getByText("failed").className).toContain("task-review-tab__status--failed");
|
||||||
|
expect(container.querySelector(".task-review-tab__header")).not.toBeNull();
|
||||||
|
expect(container.querySelector(".task-review-tab__summary-group")).not.toBeNull();
|
||||||
|
expect(container.querySelector(".task-review-tab__actions")).not.toBeNull();
|
||||||
|
expect(container.querySelector(".task-review-tab__refresh-meta")).not.toBeNull();
|
||||||
|
expect(container.querySelector(".task-review-tab__list")).not.toBeNull();
|
||||||
|
expect(container.querySelector(".task-review-tab__item-header")).not.toBeNull();
|
||||||
|
expect(container.querySelector(".task-review-tab__item-selection")).not.toBeNull();
|
||||||
|
expect(container.querySelector(".task-review-tab__item-meta-list")).not.toBeNull();
|
||||||
|
expect(container.querySelector(".task-review-tab__body")).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps review body outside the checkbox label and preserves selection on body clicks", async () => {
|
it("keeps review body outside the checkbox label and preserves selection on body clicks", async () => {
|
||||||
@@ -369,7 +381,7 @@ describe("TaskReviewTab", () => {
|
|||||||
expect(addToast).toHaveBeenCalledWith("Review refreshed", "success");
|
expect(addToast).toHaveBeenCalledWith("Review refreshed", "success");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders reviewer-agent entries in direct mode", async () => {
|
it("renders reviewer-agent entries in direct mode with populated layout hooks", async () => {
|
||||||
const task = makeTask();
|
const task = makeTask();
|
||||||
apiMocks.fetchTaskReview.mockResolvedValue({
|
apiMocks.fetchTaskReview.mockResolvedValue({
|
||||||
reviewState: {
|
reviewState: {
|
||||||
@@ -393,9 +405,11 @@ describe("TaskReviewTab", () => {
|
|||||||
emptyMessage: null,
|
emptyMessage: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
render(<TaskReviewTab task={task} addToast={vi.fn()} />);
|
const { container } = render(<TaskReviewTab task={task} addToast={vi.fn()} />);
|
||||||
expect(await screen.findByText("code review Step 2: REVISE")).toBeInTheDocument();
|
expect(await screen.findByText("code review Step 2: REVISE")).toBeInTheDocument();
|
||||||
expect(screen.getAllByText("REVISE").length).toBeGreaterThan(0);
|
expect(screen.getAllByText("REVISE").length).toBeGreaterThan(0);
|
||||||
|
expect(container.querySelector(".task-review-tab__item-header")).not.toBeNull();
|
||||||
|
expect(container.querySelector(".task-review-tab__item-meta-list")).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders all persisted addressing progress states from snapshots", async () => {
|
it("renders all persisted addressing progress states from snapshots", async () => {
|
||||||
@@ -485,14 +499,20 @@ describe("TaskReviewTab", () => {
|
|||||||
expect(screen.getByText(/Error: Patch failed/)).toBeInTheDocument();
|
expect(screen.getByText(/Error: Patch failed/)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps mobile actions wrapping contract and removes equal-width flex buttons", async () => {
|
it("keeps mobile actions wrapping contract, stacks header groups, and prevents body overflow regressions", async () => {
|
||||||
const css = await loadAllAppCss();
|
const css = await loadAllAppCss();
|
||||||
const mobileMediaStart = css.indexOf("@media (max-width: 768px)");
|
const mobileMediaStart = css.indexOf("@media (max-width: 768px)");
|
||||||
expect(mobileMediaStart).toBeGreaterThanOrEqual(0);
|
expect(mobileMediaStart).toBeGreaterThanOrEqual(0);
|
||||||
const mobileCss = css.slice(mobileMediaStart);
|
const mobileCss = css.slice(mobileMediaStart);
|
||||||
|
|
||||||
expect(mobileCss).toMatch(/\.task-review-tab__actions\s*\{[^}]*flex-wrap\s*:\s*wrap\s*;[^}]*\}/);
|
expect(mobileCss).toMatch(/\.task-review-tab__header\s*\{[^}]*flex-direction\s*:\s*column\s*;[^}]*\}/);
|
||||||
|
expect(mobileCss).toMatch(/\.task-review-tab__actions\s*\{[^}]*justify-content\s*:\s*flex-start\s*;[^}]*\}/);
|
||||||
|
expect(mobileCss).toMatch(/\.task-review-tab__actions\s+\.btn\s*\{[^}]*width\s*:\s*100%\s*;[^}]*\}/);
|
||||||
|
expect(mobileCss).toMatch(/\.task-review-tab__body\s*\{[^}]*padding\s*:\s*var\(--space-sm\)\s*;[^}]*\}/);
|
||||||
expect(mobileCss).not.toMatch(/\.task-review-tab__actions\s+\.btn\s*\{[^}]*flex\s*:\s*1\s*;[^}]*\}/);
|
expect(mobileCss).not.toMatch(/\.task-review-tab__actions\s+\.btn\s*\{[^}]*flex\s*:\s*1\s*;[^}]*\}/);
|
||||||
|
|
||||||
|
expect(css).toMatch(/\.task-review-tab__body\s*\{[^}]*overflow-x\s*:\s*auto\s*;[^}]*overflow-wrap\s*:\s*anywhere\s*;[^}]*\}/);
|
||||||
|
expect(css).toMatch(/\.task-review-tab__item\s*\{[^}]*padding\s*:\s*var\(--card-padding\)\s*;[^}]*\}/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows create PR action when in-review without prInfo and auth is available", async () => {
|
it("shows create PR action when in-review without prInfo and auth is available", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user