@@ -23,6 +23,32 @@ import { USER_DATASET } from "../../../../app-routing.constant";
2323import { UserDatasetVersionCreatorComponent } from "./user-dataset-explorer/user-dataset-version-creator/user-dataset-version-creator.component" ;
2424import { SortMethod } from "../../../type/sort-method" ;
2525import { User } from "../../../../common/type/user" ;
26+ import { ComponentFixture , TestBed } from "@angular/core/testing" ;
27+ import { By } from "@angular/platform-browser" ;
28+ import { NgIf , NgTemplateOutlet } from "@angular/common" ;
29+ import { Router } from "@angular/router" ;
30+ import { RouterTestingModule } from "@angular/router/testing" ;
31+ import { HttpClientTestingModule } from "@angular/common/http/testing" ;
32+ import { NoopAnimationsModule } from "@angular/platform-browser/animations" ;
33+ import { NzModalService } from "ng-zorro-antd/modal" ;
34+ import { NzMessageService } from "ng-zorro-antd/message" ;
35+ import { en_US , NZ_I18N } from "ng-zorro-antd/i18n" ;
36+ import { UserService } from "../../../../common/service/user/user.service" ;
37+ import { StubUserService } from "../../../../common/service/user/stub-user.service" ;
38+ import { SearchService } from "../../../service/user/search.service" ;
39+ import { DatasetService } from "../../../service/user/dataset/dataset.service" ;
40+ import { WorkflowPersistService } from "../../../../common/service/workflow-persist/workflow-persist.service" ;
41+ import { StubWorkflowPersistService } from "../../../../common/service/workflow-persist/stub-workflow-persist.service" ;
42+ import { UserProjectService } from "../../../service/user/project/user-project.service" ;
43+ import { StubUserProjectService } from "../../../service/user/project/stub-user-project.service" ;
44+ import { OperatorMetadataService } from "../../../../workspace/service/operator-metadata/operator-metadata.service" ;
45+ import { StubOperatorMetadataService } from "../../../../workspace/service/operator-metadata/stub-operator-metadata.service" ;
46+ import { FiltersComponent } from "../filters/filters.component" ;
47+ import { FiltersInstructionsComponent } from "../filters-instructions/filters-instructions.component" ;
48+ import { SearchResultsComponent } from "../search-results/search-results.component" ;
49+ import { CardItemComponent } from "../list-item/card-item/card-item.component" ;
50+ import { DashboardEntry } from "../../../type/dashboard-entry" ;
51+ import { commonTestProviders } from "../../../../common/testing/test-utils" ;
2652
2753type LoadMoreFn = ( start : number , count : number ) => Promise < { entries : any [ ] ; more : boolean } > ;
2854
@@ -361,3 +387,99 @@ describe("UserDatasetComponent", () => {
361387 } ) ;
362388 } ) ;
363389} ) ;
390+
391+ // Renders the real (instrumented) UserDatasetComponent template so its view-toggle
392+ // buttons and `#datasetCardTpl` markup are exercised for coverage. We deliberately do
393+ // NOT override UserDatasetComponent itself (that would JIT-recompile it without
394+ // instrumentation); instead each heavy child component is stripped to an empty shell so
395+ // the test does not drag in their ng-zorro internals (menus, date pickers, locale data).
396+ describe ( "UserDatasetComponent template" , ( ) => {
397+ const VIEW_MODE_KEY = "texera.userDataset.viewMode" ;
398+ let fixture : ComponentFixture < UserDatasetComponent > ;
399+ let component : UserDatasetComponent ;
400+
401+ const makeDatasetEntry = ( overrides : Partial < DashboardEntry > = { } ) : DashboardEntry =>
402+ ( {
403+ id : 5 ,
404+ name : "ds" ,
405+ description : "" ,
406+ type : "dataset" ,
407+ dataset : { isOwner : true } ,
408+ accessibleUserIds : [ ] ,
409+ likeCount : 0 ,
410+ viewCount : 0 ,
411+ isLiked : false ,
412+ size : 0 ,
413+ ...overrides ,
414+ } ) as unknown as DashboardEntry ;
415+
416+ beforeEach ( async ( ) => {
417+ localStorage . removeItem ( VIEW_MODE_KEY ) ; // viewType then defaults to "card"
418+
419+ await TestBed . configureTestingModule ( {
420+ imports : [ UserDatasetComponent , HttpClientTestingModule , NoopAnimationsModule , RouterTestingModule ] ,
421+ providers : [
422+ NzModalService ,
423+ { provide : NZ_I18N , useValue : en_US } ,
424+ { provide : UserService , useClass : StubUserService } ,
425+ { provide : WorkflowPersistService , useValue : new StubWorkflowPersistService ( [ ] ) } ,
426+ { provide : UserProjectService , useClass : StubUserProjectService } ,
427+ { provide : OperatorMetadataService , useClass : StubOperatorMetadataService } ,
428+ { provide : Router , useValue : { navigate : vi . fn ( ) } } ,
429+ { provide : SearchService , useValue : { executeSearch : vi . fn ( ( ) => of ( { entries : [ ] , more : false } ) ) } } ,
430+ {
431+ provide : DatasetService ,
432+ useValue : { getDatasetCoverUrl : vi . fn ( ( ) => of ( { url : null } ) ) , deleteDatasets : vi . fn ( ( ) => of ( { } ) ) } ,
433+ } ,
434+ { provide : NzMessageService , useValue : { warning : vi . fn ( ) } } ,
435+ ...commonTestProviders ,
436+ ] ,
437+ } )
438+ . overrideComponent ( FiltersComponent , { set : { template : "" , imports : [ ] } } )
439+ . overrideComponent ( FiltersInstructionsComponent , { set : { template : "" , imports : [ ] } } )
440+ . overrideComponent ( CardItemComponent , { set : { template : "" , imports : [ ] } } )
441+ . overrideComponent ( SearchResultsComponent , {
442+ set : {
443+ // Render the parent-supplied card template for the first entry so the
444+ // `#datasetCardTpl` body (which lives in the instrumented parent template) runs.
445+ template :
446+ '<ng-container *ngIf="viewMode === \'card\' && cardTemplate && entries.length" [ngTemplateOutlet]="cardTemplate" [ngTemplateOutletContext]="{ $implicit: entries[0] }"></ng-container>' ,
447+ imports : [ NgIf , NgTemplateOutlet ] ,
448+ } ,
449+ } )
450+ . compileComponents ( ) ;
451+
452+ fixture = TestBed . createComponent ( UserDatasetComponent ) ;
453+ component = fixture . componentInstance ;
454+ fixture . detectChanges ( ) ;
455+ } ) ;
456+
457+ afterEach ( ( ) => localStorage . removeItem ( VIEW_MODE_KEY ) ) ;
458+
459+ const buttonByTitle = ( title : string ) : HTMLButtonElement =>
460+ Array . from ( fixture . nativeElement . querySelectorAll ( "button" ) ) . find (
461+ ( b : any ) => b . getAttribute ( "title" ) === title
462+ ) as HTMLButtonElement ;
463+
464+ it ( "renders both view-mode toggles and switches viewType on click" , ( ) => {
465+ expect ( component . viewType ) . toBe ( "card" ) ;
466+ expect ( buttonByTitle ( "List View" ) ) . toBeTruthy ( ) ;
467+ expect ( buttonByTitle ( "Card View" ) ) . toBeTruthy ( ) ;
468+
469+ buttonByTitle ( "List View" ) . click ( ) ;
470+ fixture . detectChanges ( ) ;
471+ expect ( component . viewType ) . toBe ( "list" ) ;
472+
473+ buttonByTitle ( "Card View" ) . click ( ) ;
474+ fixture . detectChanges ( ) ;
475+ expect ( component . viewType ) . toBe ( "card" ) ;
476+ } ) ;
477+
478+ it ( "renders the dataset card template for each entry in card view" , ( ) => {
479+ component . searchResultsComponent . entries = [ makeDatasetEntry ( ) ] ;
480+ fixture . detectChanges ( ) ;
481+
482+ const cards = fixture . debugElement . queryAll ( By . css ( "texera-card-item" ) ) ;
483+ expect ( cards . length ) . toBe ( 1 ) ;
484+ } ) ;
485+ } ) ;
0 commit comments