feat: release 2.4.0 with original cases and automatic human review
This commit is contained in:
1 parent
baf069854b
commit
4dc2057d43
66 files changed
+1390
-206
No files matched your search
+95
-64
@@ -1,4 +1,4 @@
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { TestBed, ComponentFixture, fakeAsync, flushMicrotasks, tick } from '@angular/core/testing';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
||||
import { TaskExtensionPanelsComponent } from './task-extension-panels.component';
|
||||
@@ -50,7 +50,7 @@ describe('步骤回放工作区扩展', () => {
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
it('does not request extensions in video mode and shows no controls without a plugin', () => {
|
||||
it('does not request disabled extensions and shows no controls without a plugin', () => {
|
||||
fixture.componentRef.setInput('sessionId', 'task-one');
|
||||
fixture.detectChanges();
|
||||
http.expectNone('/api/task-extensions/task-one');
|
||||
@@ -87,87 +87,92 @@ describe('步骤回放工作区扩展', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('allows partial manual selection and keeps unfilled outcomes blank', () => {
|
||||
it('automatically saves choices and clears while keeping unfilled outcomes blank', fakeAsync(() => {
|
||||
open(); toggle('right');
|
||||
expect(fixture.nativeElement.querySelector('select')).toBeNull();
|
||||
const groups: NodeListOf<HTMLElement> = fixture.nativeElement.querySelectorAll('.choice-group');
|
||||
expect(fixture.nativeElement.querySelector('[aria-pressed="true"]')).toBeNull();
|
||||
const pass = groups[0].querySelector<HTMLButtonElement>('.pass')!;
|
||||
const failed = groups[0].querySelector<HTMLButtonElement>('.failed')!;
|
||||
const pass: HTMLButtonElement = fixture.nativeElement.querySelector('.choice-button.pass');
|
||||
pass.click(); fixture.detectChanges();
|
||||
expect(pass.getAttribute('aria-pressed')).toBe('true');
|
||||
pass.click(); fixture.detectChanges();
|
||||
expect(component.drafts['review'].values['accuracy']).toBe('');
|
||||
failed.click(); pass.click(); fixture.detectChanges();
|
||||
expect(failed.getAttribute('aria-pressed')).toBe('false');
|
||||
expect(groups[1].querySelector('[aria-pressed="true"]')).toBeNull();
|
||||
const note: HTMLTextAreaElement = fixture.nativeElement.querySelector('textarea');
|
||||
note.value = '人工核对'; note.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
fixture.nativeElement.querySelector('button.save').click();
|
||||
const request = http.expectOne('/api/task-extensions/task-one/review');
|
||||
expect(saved().values).toEqual({ accuracy: 'Pass', note: '', correctness: '' });
|
||||
pass.click(); fixture.detectChanges(); expect(saved(2).values.accuracy).toBe('');
|
||||
expect(fixture.nativeElement.textContent).toContain('人工评审已自动保存');
|
||||
}));
|
||||
|
||||
function saved(revision = 1, id = 'task-one') {
|
||||
const request = http.expectOne(`/api/task-extensions/${id}/review`);
|
||||
expect(request.request.method).toBe('PUT');
|
||||
expect(request.request.body.values).toEqual({ accuracy: 'Pass', note: '人工核对', correctness: '' });
|
||||
expect(request.request.body.expected_revision).toBe(0);
|
||||
const data = snapshot(); data.plugins[0].revision = 1; data.plugins[0].values = request.request.body.values;
|
||||
request.flush(data); fixture.detectChanges();
|
||||
expect(fixture.nativeElement.textContent).toContain('人工评审已保存');
|
||||
});
|
||||
const body = request.request.body, data = snapshot(id);
|
||||
data.plugins[0].revision = revision; data.plugins[0].values = body.values;
|
||||
request.flush(data); flushMicrotasks(); fixture.detectChanges(); return body;
|
||||
}
|
||||
|
||||
it('preserves draft and request receipt after a failed save, and permits clearing a result', () => {
|
||||
open(); toggle('right');
|
||||
const plugin = component.data()!.plugins[0];
|
||||
component.edit('review', 'accuracy', 'Failed'); component.save(plugin);
|
||||
const first = http.expectOne('/api/task-extensions/task-one/review');
|
||||
const requestId = first.request.body.request_id;
|
||||
first.flush({}, { status: 503, statusText: 'Unavailable' });
|
||||
component.save(plugin);
|
||||
const retry = http.expectOne('/api/task-extensions/task-one/review');
|
||||
expect(retry.request.body.request_id).toBe(requestId);
|
||||
retry.flush({}, { status: 409, statusText: 'Conflict' });
|
||||
it('debounces notes and never posts just from opening a task', fakeAsync(() => {
|
||||
open(); toggle('right'); tick(700); http.expectNone('/api/task-extensions/task-one/review');
|
||||
component.edit('review', 'note', '第一段'); tick(400);
|
||||
component.edit('review', 'note', '完整备注'); tick(599);
|
||||
http.expectNone('/api/task-extensions/task-one/review'); tick(1);
|
||||
expect(saved().values.note).toBe('完整备注');
|
||||
}));
|
||||
|
||||
it('does not overwrite an automatic save with an older read response', fakeAsync(() => {
|
||||
open(); component.edit('review', 'note', '已保存的新内容'); tick(600); saved(2);
|
||||
component.refresh(); fixture.detectChanges();
|
||||
http.expectOne('/api/task-extensions/task-one').flush(snapshot()); fixture.detectChanges();
|
||||
expect(component.drafts['review'].values['note']).toBe('已保存的新内容');
|
||||
expect(component.drafts['review'].revision).toBe(2);
|
||||
}));
|
||||
|
||||
it('flushes a pending note when the review panel is closed', fakeAsync(() => {
|
||||
open(); component.edit('review', 'note', '关闭前保存'); fixture.destroy();
|
||||
const request = http.expectOne('/api/task-extensions/task-one/review');
|
||||
expect(request.request.body.values.note).toBe('关闭前保存');
|
||||
const response = snapshot(); response.plugins[0].revision = 1; response.plugins[0].values = request.request.body.values;
|
||||
request.flush(response); flushMicrotasks();
|
||||
}));
|
||||
|
||||
it('preserves draft and request receipt on failure, and retries only when requested', fakeAsync(() => {
|
||||
open(); toggle('right'); const plugin = component.data()!.plugins[0];
|
||||
component.edit('review', 'accuracy', 'Failed'); void component.save(plugin);
|
||||
const first = http.expectOne('/api/task-extensions/task-one/review'), requestId = first.request.body.request_id;
|
||||
first.flush({}, { status: 503, statusText: 'Unavailable' }); flushMicrotasks(); tick(700);
|
||||
http.expectNone('/api/task-extensions/task-one/review'); void component.save(plugin);
|
||||
const retry = http.expectOne('/api/task-extensions/task-one/review'); expect(retry.request.body.request_id).toBe(requestId);
|
||||
retry.flush({}, { status: 409, statusText: 'Conflict' }); flushMicrotasks();
|
||||
expect(component.drafts['review'].values['accuracy']).toBe('Failed');
|
||||
component.edit('review', 'accuracy', ''); component.save(plugin);
|
||||
const clear = http.expectOne('/api/task-extensions/task-one/review');
|
||||
expect(clear.request.body.values.accuracy).toBe('');
|
||||
expect(clear.request.body.request_id).not.toBe(requestId);
|
||||
clear.flush(snapshot());
|
||||
});
|
||||
component.edit('review', 'accuracy', ''); void component.save(plugin);
|
||||
const clear = saved(); expect(clear.values.accuracy).toBe(''); expect(clear.request_id).not.toBe(requestId);
|
||||
}));
|
||||
|
||||
it('renders Chinese outcomes as mutually exclusive buttons and saves their declared values', () => {
|
||||
const data = snapshot(); data.plugins[0].workspace.fields[0].options = ['通过', '失败'];
|
||||
open(data); toggle('right');
|
||||
it('serializes rapid edits and saves the latest choice using the returned revision', fakeAsync(() => {
|
||||
const data = snapshot(); data.plugins[0].workspace.fields[0].options = ['通过', '失败']; open(data); toggle('right');
|
||||
const pass: HTMLButtonElement = fixture.nativeElement.querySelector('.choice-button.pass');
|
||||
const failed: HTMLButtonElement = fixture.nativeElement.querySelector('.choice-button.failed');
|
||||
expect(pass.textContent).toContain('通过'); expect(failed.textContent).toContain('失败');
|
||||
pass.click(); failed.click(); fixture.detectChanges();
|
||||
expect(component.drafts['review'].values['accuracy']).toBe('失败');
|
||||
expect(pass.getAttribute('aria-pressed')).toBe('false');
|
||||
expect(failed.getAttribute('aria-pressed')).toBe('true');
|
||||
});
|
||||
expect(component.drafts['review'].values['accuracy']).toBe('失败'); expect(saved().values.accuracy).toBe('通过');
|
||||
const second = saved(2); expect(second.values.accuracy).toBe('失败'); expect(second.expected_revision).toBe(1);
|
||||
expect(component.drafts['review'].dirty).toBeFalse();
|
||||
}));
|
||||
|
||||
it('does not apply a delayed save response to the task now displayed', () => {
|
||||
open(); toggle('right');
|
||||
component.edit('review', 'note', '只属于第一个任务'); component.save(component.data()!.plugins[0]);
|
||||
it('does not apply a delayed save response to the task now displayed', fakeAsync(() => {
|
||||
open(); toggle('right'); component.edit('review', 'note', '只属于第一个任务'); void component.save(component.data()!.plugins[0]);
|
||||
const oldSave = http.expectOne('/api/task-extensions/task-one/review');
|
||||
fixture.componentRef.setInput('sessionId', 'task-two'); fixture.detectChanges();
|
||||
http.expectOne('/api/task-extensions/task-two').flush(snapshot('task-two')); fixture.detectChanges();
|
||||
oldSave.flush(snapshot('task-one')); fixture.detectChanges();
|
||||
expect(component.data()!.session_id).toBe('task-two');
|
||||
expect(component.drafts['review'].values['note']).toBe('');
|
||||
});
|
||||
oldSave.flush(snapshot('task-one')); flushMicrotasks(); fixture.detectChanges();
|
||||
expect(component.data()!.session_id).toBe('task-two'); expect(component.drafts['review'].values['note']).toBe('');
|
||||
}));
|
||||
|
||||
it('keeps unsaved drafts across task switches and removes panels on plugin disable', () => {
|
||||
open(); toggle('right'); component.edit('review', 'note', '保留草稿');
|
||||
it('flushes a pending note on direct task switches and hides disabled plugins', fakeAsync(() => {
|
||||
open(); toggle('right'); component.edit('review', 'note', '自动保存草稿');
|
||||
fixture.componentRef.setInput('sessionId', 'task-two'); fixture.detectChanges();
|
||||
http.expectOne('/api/task-extensions/task-two').flush(snapshot('task-two'));
|
||||
const body = saved(1, 'task-one'); expect(body.values.note).toBe('自动保存草稿');
|
||||
fixture.componentRef.setInput('sessionId', 'task-one'); fixture.detectChanges();
|
||||
http.expectOne('/api/task-extensions/task-one').flush(snapshot()); fixture.detectChanges();
|
||||
expect(component.drafts['review'].values['note']).toBe('保留草稿');
|
||||
const first = snapshot(); first.plugins[0].revision = 1; first.plugins[0].values = body.values;
|
||||
http.expectOne('/api/task-extensions/task-one').flush(first); fixture.detectChanges();
|
||||
expect(component.drafts['review'].values['note']).toBe('自动保存草稿');
|
||||
window.dispatchEvent(new Event('artemis-plugin-change')); fixture.detectChanges();
|
||||
http.expectOne('/api/task-extensions/task-one').flush({ ...snapshot(), plugins: [] }); fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('nav')).toBeNull();
|
||||
http.expectOne('/api/task-extensions/task-one').flush({ ...first, plugins: [] }); fixture.detectChanges();
|
||||
expect(fixture.nativeElement.querySelector('aside')).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('keeps running tasks read-only then enables editing when the task finishes', () => {
|
||||
open({ ...snapshot(), editable: false }); toggle('right');
|
||||
@@ -187,4 +192,30 @@ describe('步骤回放工作区扩展', () => {
|
||||
expect(component.panels('right').length).toBe(1);
|
||||
} finally { Object.defineProperty(window, 'innerWidth', original); }
|
||||
});
|
||||
|
||||
it('shows original case sections in order without substituting the AI prompt', () => {
|
||||
const data = snapshot();
|
||||
data.plugins[0].workspace.panels[0].kind = 'original_case';
|
||||
data.case_title = 'CASE-001 检查设置';
|
||||
data.original_case = { preconditions: '已登录', steps: '1. 打开设置\n2. 核对版本', expected: '版本可见' };
|
||||
open(data); toggle('left');
|
||||
const text = fixture.nativeElement.textContent;
|
||||
expect(text).toContain('CASE-001 检查设置');
|
||||
expect(text).toMatch(/预置条件[\s\S]*已登录[\s\S]*操作步骤[\s\S]*核对版本[\s\S]*预期结果[\s\S]*版本可见/);
|
||||
expect(text).not.toContain('原始需求');
|
||||
});
|
||||
|
||||
it('saves before navigation and keeps the current task on save failure', fakeAsync(() => {
|
||||
const first = snapshot(); first.navigation = { scope: 'batch', position: 1, total: 2, previous: null, next: 'task-two' };
|
||||
open(first); toggle('right'); const emitted = jasmine.createSpy('navigate'); component.navigate.subscribe(emitted);
|
||||
const saveButton = fixture.nativeElement.querySelector('button.save'), navigation = fixture.nativeElement.querySelector('.review-navigation');
|
||||
expect(saveButton.compareDocumentPosition(navigation) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
component.edit('review', 'note', '切换前保存'); void component.move('next'); expect(emitted).not.toHaveBeenCalled();
|
||||
http.expectOne('/api/task-extensions/task-one/review').flush({}, {status: 409, statusText: 'Conflict'});
|
||||
flushMicrotasks(); expect(emitted).not.toHaveBeenCalled(); expect(component.drafts['review'].dirty).toBeTrue();
|
||||
void component.move('next'); saved(); flushMicrotasks(); expect(emitted).toHaveBeenCalledOnceWith('task-two');
|
||||
fixture.componentRef.setInput('sessionId', 'task-two'); fixture.detectChanges();
|
||||
http.expectOne('/api/task-extensions/task-two').flush(snapshot('task-two')); fixture.detectChanges();
|
||||
expect(component.panels('right').length).toBe(1);
|
||||
}));
|
||||
});
|
||||
Reference in new issue
Block a user