import { TestBed, fakeAsync, flushMicrotasks } from '@angular/core/testing'; import { signal } from '@angular/core'; import { Router } from '@angular/router'; import { FeaturedTasksComponent } from './featured-tasks.component'; import { FavoritesService, Favorite } from '../../services/favorites.service'; import { AgentService } from '../../services/agent.service'; import { SystemService } from '../../services/system.service'; describe('Featured tasks',()=>{ let api:any, agent:any, system:any, router:any; const favorite:Favorite={id:'favorite',name:'检查页面',kind:'task',source_kind:'task',source_id:'source',inherited_fields:[],snapshot:{goal:'检查主页',runtime_overrides:{profile:'pro',model:'original-model',reasoning_effort:'high',device_serial:'device'}}}; beforeEach(()=>{ api={items:signal([structuredClone(favorite)]),loaded:signal(true),loading:signal(false),error:signal(''),load:jasmine.createSpy().and.resolveTo(),run:jasmine.createSpy().and.resolveTo({kind:'task',session_id:'new'}),update:jasmine.createSpy().and.resolveTo(favorite),remove:jasmine.createSpy().and.resolveTo()}; agent={fetchSessions:jasmine.createSpy(),selectSession:jasmine.createSpy(),batchFilter:signal(''),activeTab:signal('tasks')};system={connectedDevices:signal([{serial:'device',state:'device'}])};router={navigate:jasmine.createSpy().and.resolveTo(true)}; TestBed.configureTestingModule({providers:[{provide:FavoritesService,useValue:api},{provide:AgentService,useValue:agent},{provide:SystemService,useValue:system},{provide:Router,useValue:router}]}); }); it('runs a card with original settings and keeps configuration folded initially',fakeAsync(()=>{ const fixture=TestBed.createComponent(FeaturedTasksComponent);fixture.detectChanges(); expect(fixture.nativeElement.querySelector('.configuration')).toBeNull(); fixture.nativeElement.querySelector('.primary').click();flushMicrotasks(); expect(api.run).toHaveBeenCalledWith('favorite',{});expect(api.update).not.toHaveBeenCalled();expect(agent.selectSession).toHaveBeenCalledWith('new',true);expect(router.navigate).toHaveBeenCalledWith(['/workspace']); })); it('blocks unavailable original devices and opens a device chooser without submitting',fakeAsync(()=>{ system.connectedDevices.set([{serial:'other',state:'device'}]);const fixture=TestBed.createComponent(FeaturedTasksComponent);fixture.detectChanges();fixture.nativeElement.querySelector('.primary').click();flushMicrotasks();fixture.detectChanges(); expect(api.run).not.toHaveBeenCalled();expect(fixture.nativeElement.querySelector('[aria-label="复跑设备"]')).not.toBeNull();expect(fixture.nativeElement.querySelector('[role="alert"]').textContent).toContain('原设备不在线'); })); it('applies model changes only to this run unless defaults are explicitly saved',fakeAsync(()=>{ const fixture=TestBed.createComponent(FeaturedTasksComponent);const c=fixture.componentInstance;c.draft(favorite).model='temporary';void c.run(favorite);flushMicrotasks(); expect(api.run).toHaveBeenCalledWith('favorite',{task_config:{model:'temporary'}});expect(api.update).not.toHaveBeenCalled();expect(favorite.snapshot.runtime_overrides!['model']).toBe('original-model'); void c.save(favorite);flushMicrotasks();expect(api.update).toHaveBeenCalledWith('favorite',{task_config:{model:'temporary'},name:'检查页面'}); })); it('uses the shared model picker and automatically keeps completed legacy settings without a warning',fakeAsync(()=>{ api.items.set([{...structuredClone(favorite),inherited_fields:['record_video','recording']}]); const fixture=TestBed.createComponent(FeaturedTasksComponent);fixture.detectChanges(); expect(fixture.nativeElement.textContent).not.toContain('旧任务部分配置缺失'); fixture.nativeElement.querySelector('.configure').click();fixture.detectChanges();flushMicrotasks(); const select:HTMLSelectElement=fixture.nativeElement.querySelector('app-model-selector select'); expect(Array.from(select.options,option=>option.value)).toEqual(['gpt-6-luna','gpt-6.1-sol','gpt-6-astra','mimo-v2.6-pro','__custom__']); expect(select.value).toBe('__custom__'); expect(fixture.nativeElement.querySelector('input[aria-label="复跑模型(自定义)"]').value).toBe('original-model'); select.value='gpt-6-luna';select.dispatchEvent(new Event('change'));fixture.detectChanges(); fixture.nativeElement.querySelector('.primary').click();flushMicrotasks(); expect(api.run).toHaveBeenCalledWith('favorite',{task_config:{model:'gpt-6-luna'}}); expect(api.update).not.toHaveBeenCalled(); })); it('computer-only favorites need no Android device and can be filtered by name',fakeAsync(()=>{ const pc:Favorite={...structuredClone(favorite),kind:'exploration_case',case_version:1,snapshot:{goal:'检查电脑',runtime_overrides:{profile:'flash',model:'original-model',reasoning_effort:'high',execution_target:'computer'}}};api.items.set([pc]);system.connectedDevices.set([]); const fixture=TestBed.createComponent(FeaturedTasksComponent),c=fixture.componentInstance;c.query.set('不存在');expect(c.filtered()).toEqual([]);c.query.set('页面');expect(c.filtered().length).toBe(1);void c.run(pc);flushMicrotasks();expect(api.run).toHaveBeenCalled(); })); it('batch defaults keep each case and repetition count, with explicit uniform override',()=>{ const batch:Favorite={...structuredClone(favorite),kind:'batch',snapshot:{repeat_count:3,cases:[{name:'甲',goal:'检查甲',task_config:{profile:'flash',model:'a',reasoning_effort:'high',device_serial:'device'}},{name:'乙',goal:'检查乙',task_config:{profile:'pro',model:'b',reasoning_effort:'max',device_serial:'device'}}]}}; const fixture=TestBed.createComponent(FeaturedTasksComponent),c=fixture.componentInstance;expect(c.payload(batch)).toEqual({});const d=c.draft(batch);expect(c.payload(batch)).toEqual({case_configs:{}});d.uniform=true;d.model='uniform';d.reasoning_effort='high';expect(c.payload(batch)['task_config']).toEqual({model:'uniform',reasoning_effort:'high'});expect(batch.snapshot.repeat_count).toBe(3); }); });