This is an automated email from the ASF dual-hosted git repository.
github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git
The following commit(s) were added to refs/heads/main by this push:
new ae17c8fafb test(frontend): render ResultExportationComponent template
branches for coverage (#7365)
ae17c8fafb is described below
commit ae17c8fafbb213897579a574519004bf0d66ad20
Author: Meng Wang <[email protected]>
AuthorDate: Fri Aug 7 04:24:13 2026 -0700
test(frontend): render ResultExportationComponent template branches for
coverage (#7365)
### What changes were proposed in this PR?
Extends `ResultExportationComponent`'s spec so the **template** actually
renders
each of its branches. The class was already fully unit-tested, but the
existing
tests drive the class directly and never render most of the dialog,
leaving its
`*ngIf` / `*ngFor` / `(click)` / `[(ngModel)]` constructs unexecuted. No
production code was changed.
6 tests, each putting the component in a state the template switches on
and then
calling `detectChanges()`:
- the restricted-export error `nz-alert` (every operator blocked);
- the partial-skip warning `nz-alert` (some but not all operators
blocked);
- the export-type `nz-select` and its output-gated `nz-option` arms
(`isTableOutput` / `isVisualizationOutput` / `containsBinaryData`);
- the filename input when `exportType === "data"`;
- the local **Export** button — driven through the DOM
(`triggerEventHandler("click")`) and asserting the export service double
was
called with the `local` destination;
- the dataset destination — the search input's `(input)` handler, the
`*ngFor` dataset list, and the **Create New Dataset** button (asserts
the
modal-service double opens the creator).
Per the component's determinism notes: no fake timers, no
layout/geometry
assertions, and no timezone-sensitive date assertions — the tests assert
on
rendered text, element presence, and the injected service doubles.
### Any related issues, documentation, discussions?
Closes #7361
### How was this PR tested?
Extended unit tests, run locally in `frontend/` (all green; the failure
path was
verified by breaking an assertion to confirm the suite goes red):
```
ng test --watch=false --include
src/app/workspace/component/result-exportation/result-exportation.component.spec.ts
# Test Files 1 passed (1) | Tests 26 passed (26)
prettier --write <spec> # clean
eslint <spec> # clean
```
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8 [1M context])
---
.../result-exportation.component.spec.ts | 107 +++++++++++++++++++++
1 file changed, 107 insertions(+)
diff --git
a/frontend/src/app/workspace/component/result-exportation/result-exportation.component.spec.ts
b/frontend/src/app/workspace/component/result-exportation/result-exportation.component.spec.ts
index 53d0aeb13e..47c84273f4 100644
---
a/frontend/src/app/workspace/component/result-exportation/result-exportation.component.spec.ts
+++
b/frontend/src/app/workspace/component/result-exportation/result-exportation.component.spec.ts
@@ -18,6 +18,7 @@
*/
import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { By } from "@angular/platform-browser";
import { of } from "rxjs";
import { ResultExportationComponent } from "./result-exportation.component";
import {
@@ -367,6 +368,112 @@ describe("ResultExportationComponent", () => {
expect(component.userAccessibleDatasets).toBe(before);
expect(component.inputDatasetName).toBe(nameBefore);
});
+
+ // Renders the template in each of the states it switches on so the *ngIf /
*ngFor /
+ // (click) / [(ngModel)] constructs actually execute. detectChanges() is the
coverage switch.
+ describe("template rendering", () => {
+ function setAllOperators(ids: string[]): void {
+ const graph = TestBed.inject(WorkflowActionService) as unknown as {
+ getTexeraGraph: ReturnType<typeof vi.fn>;
+ };
+ graph.getTexeraGraph.mockReturnValue({
+ getAllOperators: () => ids.map(id => ({ operatorID: id })),
+ });
+ }
+
+ function restrict(entries: Record<string, string[]>): void {
+ const map = new Map<string, Set<string>>();
+ Object.entries(entries).forEach(([op, labels]) => map.set(op, new
Set(labels)));
+ component.downloadability = new WorkflowResultDownloadability(map);
+ }
+
+ it("renders the restricted-export error alert when every operator is
blocked", () => {
+ setAllOperators(["op-a"]);
+ restrict({ "op-a": ["Sales ([email protected])"] });
+ fixture.detectChanges();
+
+ expect(component.isExportRestricted).toBe(true);
+ const alert = fixture.debugElement.query(By.css("nz-alert"));
+ expect(alert).toBeTruthy();
+ expect(alert.nativeElement.textContent).toContain("Export unavailable");
+ });
+
+ it("renders the partial-skip warning alert when only some operators are
blocked", () => {
+ setAllOperators(["op-a", "op-b"]);
+ restrict({ "op-a": ["Sales ([email protected])"] });
+ fixture.detectChanges();
+
+ expect(component.hasPartialNonDownloadable).toBe(true);
+ expect(fixture.nativeElement.textContent).toContain("Some operators will
be skipped");
+ });
+
+ it("renders the export-type select and its output-gated options when
export is allowed", () => {
+ setAllOperators(["op-a"]);
+ restrict({}); // nothing blocked -> not restricted
+ component.exportType = "csv"; // != "data"
+ component.isTableOutput = true;
+ component.isVisualizationOutput = true;
+ component.containsBinaryData = false;
+ fixture.detectChanges();
+
+ expect(component.isExportRestricted).toBe(false);
+
expect(fixture.debugElement.query(By.css("#exportTypeInput"))).toBeTruthy();
+ });
+
+ it("renders the filename input when the export type is 'data'", () => {
+ setAllOperators(["op-a"]);
+ restrict({});
+ component.exportType = "data";
+ fixture.detectChanges();
+
+
expect(fixture.debugElement.query(By.css("#filenameInput"))).toBeTruthy();
+ });
+
+ it("renders the local Export button and exports on click", () => {
+ setAllOperators(["op-a"]);
+ restrict({});
+ component.destination = "local";
+ fixture.detectChanges();
+
+ const exportBtn = fixture.debugElement
+ .queryAll(By.css("button"))
+ .find(btn => btn.nativeElement.textContent.trim() === "Export");
+ expect(exportBtn).toBeTruthy();
+
+ exportBtn!.triggerEventHandler("click", null);
+ expect(exportWorkflowExecutionResult).toHaveBeenCalledTimes(1);
+ const args = exportWorkflowExecutionResult.mock.calls[0];
+ expect(args[7]).toBe("local");
+ });
+
+ it("renders the dataset destination with its list and create button", ()
=> {
+ setAllOperators(["op-a"]);
+ restrict({});
+ component.destination = "dataset";
+ fixture.detectChanges();
+
+ // the dataset search input drives the (input) handler
+ const search =
fixture.debugElement.query(By.css("input[name='datasetName']"));
+ expect(search).toBeTruthy();
+ search.triggerEventHandler("input", { target: { value: "" } });
+
+ // The nz-auto-option list the *ngFor drives is bound to
+ // `filteredUserAccessibleDatasets`; assert the component fed it exactly
the
+ // WRITE dataset (the READ one is filtered out), so the list branch has
real data
+ // behind it. The option content itself only enters the DOM once the
autocomplete
+ // panel expands, which jsdom does not drive, so it is not asserted here.
+ expect(component.filteredUserAccessibleDatasets.map(d =>
d.dataset.name)).toEqual(["writable"]);
+
+ // the create-new-dataset button opens the creator modal
+ const createBtn = fixture.debugElement
+ .queryAll(By.css("button"))
+ .find(btn => btn.nativeElement.textContent.includes("Create New
Dataset"));
+ expect(createBtn).toBeTruthy();
+
+ createBtn!.triggerEventHandler("click", null);
+ expect(modalCreate).toHaveBeenCalledTimes(1);
+ });
+ });
});
describe("ResultExportationComponent (context-menu source with default modal
data)", () => {