This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-7535-61f22afc1f9fd7c4833fbf54e621dcbef9068eac in repository https://gitbox.apache.org/repos/asf/texera.git
commit dcecfa69fd9a94319f931221e7f99420e9d5970b Author: Xinyuan Lin <[email protected]> AuthorDate: Mon Aug 10 23:41:08 2026 -0700 test(frontend): render the hub workflow detail with its real children (#7535) ### What changes were proposed in this PR? `hub-workflow-detail.component.html` reported 0 of 46 lines while its own `.ts` sat at 98% — the attribution loss from #7458, not a testing gap. The spec stubs its three children out through `TestBed.overrideComponent`, and any override re-JITs the component from its decorator metadata; the re-compiled template has no source map back to the `.html`, so the bindings execute uncounted. Adds a `describe` block that renders the component with its real children. That restores attribution: | | Before | After | |---|---|---| | lines | 0/46 (0.0%) | **45/46 (97.8%)** | | branches | — | 3/3 | Three tests: the real editor and mini-map resolving rather than the stub selectors, and the `*ngIf="isHub"` back button appearing and not appearing. It keeps its own `TestBed` so the 32 tests above retain their mocked `WorkflowActionService` and the ten assertions they make on it — the real service is needed here only because the real editor injects `DynamicSchemaService`, which reads the graph's operator streams. ### Verification Both `*ngIf` mutations were applied to the template and reverted (production diff empty): | Mutation | Result | |---|---| | back button always rendered (`*ngIf="true"`) | red | | back button never rendered (`*ngIf="false"`) | red | Two assertions in the first test are honestly **guards, not behaviour pins**: renaming the child elements only breaks the template build rather than producing a clean behavioural failure, so they are there to stop the override creeping back in, and the coverage measurement above is their real evidence. Saying so rather than listing them as killed mutations. One assertion was dropped during review of my own work: the clone button's `[disabled]="!isLogin || !isHub || !isActivatedUser"` does not reflect to the DOM `disabled` property under this fixture (it stays `false` with `isHub === false`), so asserting on it would have been either vacuous or wrong. The back button discriminates cleanly and is what the tests use. No production file is touched. ### Any related issues, documentation, discussions? Closes #7534 ### How was this PR tested? ``` npx ng test --watch=false --include="**/hub-workflow-detail.component.spec.ts" ``` ``` Test Files 1 passed (1) Tests 35 passed (35) ``` 3 new on top of the existing 32. `yarn format:ci` passes. ### Was this PR authored or co-authored using generative AI tooling? Generated-by: Claude Code (Opus 5) --- .../detail/hub-workflow-detail.component.spec.ts | 101 +++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.spec.ts b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.spec.ts index cefc1e0194..2e45eaa8e0 100644 --- a/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.spec.ts +++ b/frontend/src/app/hub/component/workflow/detail/hub-workflow-detail.component.spec.ts @@ -40,6 +40,13 @@ import { MarkdownDescriptionComponent } from "../../../../dashboard/component/us import { WorkflowEditorComponent } from "../../../../workspace/component/workflow-editor/workflow-editor.component"; import { MiniMapComponent } from "../../../../workspace/component/workflow-editor/mini-map/mini-map.component"; import { commonTestProviders } from "../../../../common/testing/test-utils"; +import { DragDropModule } from "@angular/cdk/drag-drop"; +import { MarkdownService } from "ngx-markdown"; +import { + workflowEditorTestImports, + workflowEditorTestProviders, +} from "../../../../workspace/component/workflow-editor/workflow-editor.test-utils"; +import { PanelService } from "../../../../workspace/service/panel/panel.service"; @Component({ selector: "texera-markdown-description", standalone: true, template: "" }) class StubMarkdownDescriptionComponent { @@ -440,3 +447,97 @@ describe("HubWorkflowDetailComponent", () => { }); }); }); +/** + * The suite above stubs the three child components out via `TestBed.overrideComponent`, and that + * is what puts `hub-workflow-detail.component.html` at 0% coverage: any override makes Angular + * re-JIT the component from its retained decorator metadata, and the re-compiled template has no + * source map back to the .html, so every binding still executes but none is attributed. The + * component's own .ts sits at 98% while its template reports 0/46 — only attribution loss produces + * that gap (see #7458). + * + * This block renders the component with its REAL children instead, which restores attribution. It + * keeps its own TestBed so the 32 tests above keep their mocked WorkflowActionService and their + * assertions on it; the real service is needed here only because the real editor injects + * DynamicSchemaService, which reads the graph's operator streams. + */ +describe("HubWorkflowDetailComponent rendered with its real children", () => { + let fixture: ComponentFixture<HubWorkflowDetailComponent>; + + function render(opts: { isHub: boolean }): void { + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + imports: [ + HubWorkflowDetailComponent, + NzIconModule.forChild([ArrowLeftOutline, EyeOutline, LikeOutline, UserOutline]), + DragDropModule, + ...workflowEditorTestImports, + ], + providers: [ + ...workflowEditorTestProviders, + PanelService, + // The real markdown child renders <markdown>, which injects MarkdownService. + { provide: MarkdownService, useValue: { parse: (v: string) => v } }, + // isHub is false when the wid arrives as modal data and true when it comes from the route, + // which is the switch the template's two halves hang off. + { provide: NZ_MODAL_DATA, useValue: opts.isHub ? undefined : { wid: 5 } }, + { + provide: ActivatedRoute, + useValue: { snapshot: { params: opts.isHub ? { id: "5" } : {} } }, + }, + { provide: Router, useValue: { navigateByUrl: vi.fn(), navigate: vi.fn() } }, + { + provide: HubService, + useValue: { + getCounts: () => of([{ entityId: 5, entityType: EntityType.Workflow, counts: {} }]), + postView: () => of(7), + isLiked: () => of([]), + postLike: () => of(true), + postUnlike: () => of(true), + cloneWorkflow: () => of(99), + }, + }, + { + provide: WorkflowPersistService, + useValue: { + retrieveWorkflow: () => of({} as Workflow), + retrievePublicWorkflow: () => of({} as Workflow), + getOwnerName: () => of("owner"), + getWorkflowName: () => of("name"), + getWorkflowDescription: () => of("desc"), + }, + }, + { provide: NotificationService, useValue: { error: vi.fn(), success: vi.fn() } }, + { provide: UserService, useClass: StubUserService }, + ...commonTestProviders, + ], + }); + fixture = TestBed.createComponent(HubWorkflowDetailComponent); + fixture.detectChanges(); + } + + it("renders the real editor and mini-map rather than stubs", () => { + // The point of this block: if these resolve to the stubbed selectors the template is + // re-JITed and its coverage silently goes to zero again. + render({ isHub: true }); + + const host = fixture.nativeElement as HTMLElement; + expect(host.querySelector("texera-workflow-editor")).not.toBeNull(); + expect(host.querySelector("texera-mini-map")).not.toBeNull(); + }); + + it("shows the back button when the id came from the route", () => { + // Asserted on the rendered DOM rather than on `isHub`: the class flag is set either way, so a + // class-state assertion cannot tell whether the template consults it. + render({ isHub: true }); + + expect((fixture.nativeElement as HTMLElement).querySelector(".go-back-button")).not.toBeNull(); + }); + + it("hides the back button when the wid arrived as modal data", () => { + // The converse. Without it the `*ngIf` could be replaced by a constant and the positive case + // above would still pass. + render({ isHub: false }); + + expect((fixture.nativeElement as HTMLElement).querySelector(".go-back-button")).toBeNull(); + }); +});
