Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/i18n/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,14 @@ const en = {
confirm: 'Auto-reschedule',
cancel: 'Keep as is',
},
autoScheduleDlg: {
title: 'Auto-schedule',
body: 'Shift {count} task(s) later to satisfy dependency links?',
skipped: '{count} locked task(s) also violate links and were skipped.',
confirm: 'Apply',
cancel: 'Cancel',
none: 'All dependencies satisfied — nothing to reschedule.',
},
resource: {
header: 'Resource',
peak: 'Peak',
Expand Down
8 changes: 8 additions & 0 deletions packages/i18n/src/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,14 @@ const zh = {
confirm: '自动重新排程',
cancel: '保持不变',
},
autoScheduleDlg: {
title: '自动排程',
body: '将顺延 {count} 个任务以满足依赖约束,是否执行?',
skipped: '另有 {count} 项因锁定/无权限跳过。',
confirm: '执行',
cancel: '取消',
none: '依赖均满足,无需排程',
},
resource: {
header: '资源',
peak: '峰值',
Expand Down
91 changes: 91 additions & 0 deletions packages/plugin-gantt/src/GanttView.autoscheduledlg.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* Toolbar auto-schedule confirmation flow (自动排程确认弹窗): compute first,
* ask before the bulk write, transient notice when nothing violates.
*/
import React from 'react';
import { render, act, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { GanttView, type GanttTask } from './GanttView';

const D = (s: string) => new Date(s);

function makeTasks(violating: boolean): GanttTask[] {
return [
{ id: 'P', title: 'Pred', start: D('2024-06-01'), end: D('2024-06-10'), progress: 0, dependencies: [] },
{
id: 'B',
title: 'Succ',
// Violating: starts before P ends. Clean: starts after.
start: violating ? D('2024-06-05') : D('2024-06-11'),
end: violating ? D('2024-06-08') : D('2024-06-14'),
progress: 0,
dependencies: ['P'],
},
];
}

function renderView(opts: { violating: boolean; onTaskUpdate: (t: GanttTask, c: any) => void }) {
return render(
<div style={{ width: 1280, height: 600 }}>
<GanttView
tasks={makeTasks(opts.violating)}
startDate={D('2024-06-01')}
endDate={D('2024-06-30')}
onTaskUpdate={opts.onTaskUpdate}
autoSchedule
/>
</div>
);
}

const clickWand = (container: HTMLElement) => {
const btn = container.querySelector('[data-testid="gantt-auto-schedule"]') as HTMLElement;
expect(btn).toBeTruthy();
act(() => { fireEvent.click(btn); });
};

describe('auto-schedule confirmation dialog', () => {
it('computes first and asks — nothing is written before confirm', () => {
const onTaskUpdate = vi.fn();
const { container, baseElement } = renderView({ violating: true, onTaskUpdate });
clickWand(container);
const dlg = baseElement.querySelector('[data-testid="gantt-autoschedule-dialog"]');
expect(dlg).toBeTruthy();
expect(dlg!.textContent).toContain('1');
expect(onTaskUpdate).not.toHaveBeenCalled();
});

it('confirm applies the shift', async () => {
const onTaskUpdate = vi.fn();
const { container, baseElement } = renderView({ violating: true, onTaskUpdate });
clickWand(container);
await act(async () => {
fireEvent.click(baseElement.querySelector('[data-testid="gantt-autoschedule-confirm"]') as HTMLElement);
});
expect(onTaskUpdate).toHaveBeenCalledTimes(1);
const [task, changes] = onTaskUpdate.mock.calls[0];
expect(task.id).toBe('B');
expect(changes.start.toISOString()).toBe(D('2024-06-10').toISOString());
expect(baseElement.querySelector('[data-testid="gantt-autoschedule-dialog"]')).toBeFalsy();
});

it('cancel discards without writing', () => {
const onTaskUpdate = vi.fn();
const { container, baseElement } = renderView({ violating: true, onTaskUpdate });
clickWand(container);
act(() => {
fireEvent.click(baseElement.querySelector('[data-testid="gantt-autoschedule-cancel"]') as HTMLElement);
});
expect(onTaskUpdate).not.toHaveBeenCalled();
expect(baseElement.querySelector('[data-testid="gantt-autoschedule-dialog"]')).toBeFalsy();
});

it('a clean graph shows the transient notice instead of the dialog', () => {
const onTaskUpdate = vi.fn();
const { container, baseElement } = renderView({ violating: false, onTaskUpdate });
clickWand(container);
expect(baseElement.querySelector('[data-testid="gantt-autoschedule-dialog"]')).toBeFalsy();
expect(baseElement.querySelector('[data-testid="gantt-autoschedule-clean"]')).toBeTruthy();
expect(onTaskUpdate).not.toHaveBeenCalled();
});
});
Loading
Loading