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 96ed553760 test(frontend): render DatasetDetailComponent template
branches for coverage (#7419)
96ed553760 is described below
commit 96ed553760a299f2031f4cd83c024e9327cb5346
Author: Meng Wang <[email protected]>
AuthorDate: Sat Aug 8 04:13:59 2026 -0700
test(frontend): render DatasetDetailComponent template branches for
coverage (#7419)
### What changes were proposed in this PR?
Extends the existing `DatasetDetailComponent` spec so the detail view's
markup
actually renders, covering template branches that were previously never
executed
(`frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.html`).
The spec previously drove the class directly and never queried the DOM.
No
production code was changed.
9 tests drive the template through the DOM:
- **Like tag** — likes when logged in, unlikes when already liked, and
stays
inert (with the `disabled` class) when logged out, exercising the
`(click)="isLogin && toggleLike()"` guard.
- **Cover image** — the `*ngIf` omits the `<img>` without a cover URL
and the
`[src]` binding renders it when one is present.
- **Right bar** — both arms of the collapse/restore `*ngIf` pair are
clicked.
- **Settings tab** — the dataset-name `[(ngModel)]` input renders and
its Save
button routes to the service; both `nz-switch` toggles are present and
their
change handlers reach `updateDatasetPublicity` /
`updateDatasetDownloadable`.
- **Contributors** — the `*ngFor` renders the seeded contributor rows.
Three component behaviours the tests had to account for, noted in
comments so the
setup isn't mistaken for boilerplate:
- `toggleLike()` early-returns unless `currentUid` is set — the spec's
existing
`login()` helper supplies it (the stub user service emits before the
component
subscribes).
- `ngOnInit`'s subscriptions reset fields such as `coverImageUrl`, so
the helper
runs one change-detection pass first, then applies the test state, then
renders.
- `nz-tabs` only renders the active tab, and the Settings tab is
additionally
behind `*ngIf="userHasWriteAccess()"`, so an `openTab()` helper switches
tabs
and the access level is seeded.
Per the issue's determinism constraints: no fake timers are introduced,
no
date/time string is asserted, and no layout or geometry is asserted.
### Any related issues, documentation, discussions?
Closes #7409
### 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/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.spec.ts
# Test Files 1 passed (1) | Tests 104 passed (104)
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])
---
.../dataset-detail.component.spec.ts | 155 +++++++++++++++++++++
1 file changed, 155 insertions(+)
diff --git
a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.spec.ts
b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.spec.ts
index 43d9481167..63223d125f 100644
---
a/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.spec.ts
+++
b/frontend/src/app/dashboard/component/user/user-dataset/user-dataset-explorer/dataset-detail.component.spec.ts
@@ -18,6 +18,7 @@
*/
import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { By } from "@angular/platform-browser";
import { ActivatedRoute, Router } from "@angular/router";
import { of, Subject, throwError } from "rxjs";
import { NzModalService } from "ng-zorro-antd/modal";
@@ -1673,4 +1674,158 @@ describe("DatasetDetailComponent behavior", () => {
expect(datasetServiceStub.updateDatasetContributors).not.toHaveBeenCalled();
});
});
+
+ // ─── template rendering
────────────────────────────────────────────────────
+ // These drive the markup through the DOM (rather than calling handlers
directly)
+ // so the template's bindings and conditional blocks actually execute.
+ describe("template rendering", () => {
+ // Renders the component and applies the given state, so each *ngIf arm is
exercised.
+ // The first detectChanges() lets ngOnInit's subscriptions settle — they
reset fields
+ // such as coverImageUrl — so the state is applied afterwards and rendered
by a
+ // second change-detection pass.
+ const renderWith = (state: Partial<DatasetDetailComponent> = {}): void => {
+ createComponent();
+ fixture.detectChanges();
+ Object.assign(component, state);
+ fixture.detectChanges();
+ };
+
+ const clickByCss = (selector: string): void => {
+ const el = fixture.debugElement.query(By.css(selector));
+ expect(el).toBeTruthy();
+ el.triggerEventHandler("click", null);
+ fixture.detectChanges();
+ };
+
+ // nz-tabs renders only the active tab's content, so a tab must be opened
by its
+ // title before the markup inside it can be queried.
+ const openTab = (title: string): void => {
+ const tab = fixture.debugElement
+ .queryAll(By.css(".ant-tabs-tab"))
+ .find(el => (el.nativeElement.textContent ?? "").includes(title));
+ expect(tab).toBeTruthy();
+ tab!.nativeElement.click();
+ fixture.detectChanges();
+ };
+
+ it("toggles the like through the like tag when logged in", () => {
+ // toggleLike() early-returns unless currentUid is set, which login()
supplies
+ createComponent();
+ fixture.detectChanges();
+ login();
+ Object.assign(component, { isLogin: true, did: 5, isLiked: false,
likeCount: 1 });
+ fixture.detectChanges();
+
+ clickByCss(".like-tag");
+
+ expect(hubServiceStub.postLike).toHaveBeenCalled();
+ });
+
+ it("unlikes through the same tag when the dataset is already liked", () =>
{
+ createComponent();
+ fixture.detectChanges();
+ login();
+ Object.assign(component, { isLogin: true, did: 5, isLiked: true,
likeCount: 2 });
+ fixture.detectChanges();
+
+ clickByCss(".like-tag");
+
+ expect(hubServiceStub.postUnlike).toHaveBeenCalled();
+ });
+
+ it("does not toggle the like when logged out", () => {
+ renderWith({ isLogin: false, did: 5, isLiked: false, likeCount: 1 });
+
+ const likeTag = fixture.debugElement.query(By.css(".like-tag"));
+ expect(likeTag).toBeTruthy();
+ // the template guards the handler with `isLogin &&`
+ expect(likeTag.nativeElement.classList).toContain("disabled");
+
+ likeTag.triggerEventHandler("click", null);
+
+ expect(hubServiceStub.postLike).not.toHaveBeenCalled();
+ });
+
+ it("omits the cover image when there is no cover URL", () => {
+ renderWith({ coverImageUrl: null });
+
expect(fixture.debugElement.query(By.css(".dataset-cover-image"))).toBeNull();
+ });
+
+ it("renders the cover image bound to the cover URL", () => {
+ renderWith({ coverImageUrl: "blob:cover" });
+ const img = fixture.debugElement.query(By.css(".dataset-cover-image"));
+ expect(img).toBeTruthy();
+ expect(img.nativeElement.getAttribute("src")).toBe("blob:cover");
+ });
+
+ it("collapses the right bar from the template, then renders the restore
control", () => {
+ renderWith({ isRightBarCollapsed: false });
+ openTab("Versions & Files");
+
+ // both arms of the *ngIf pair are exercised: hide first, then the show
button
+ clickByCss("button[nz-tooltip='Hide the right bar']");
+ expect(component.isRightBarCollapsed).toBe(true);
+
+ clickByCss("button[nz-tooltip='Show Tree']");
+ expect(component.isRightBarCollapsed).toBe(false);
+ });
+
+ it("binds the dataset name input and saves it from the template", () => {
+ // the Settings tab is behind *ngIf="userHasWriteAccess()"
+ renderWith({ did: 5, editedDatasetName: "renamed",
userDatasetAccessLevel: "WRITE" });
+ openTab("Settings");
+
+ const input = fixture.debugElement.query(By.css(".settings-name-controls
input[nz-input]"));
+ expect(input).toBeTruthy();
+
+ // drive the [(ngModel)] update path through the DOM
+ input.nativeElement.value = "typed-name";
+ input.nativeElement.dispatchEvent(new Event("input"));
+ fixture.detectChanges();
+ expect(component.editedDatasetName).toBe("typed-name");
+
+ const saveBtn = fixture.debugElement
+ .queryAll(By.css("button"))
+ .find(btn => (btn.nativeElement.textContent ?? "").trim() === "Save");
+ expect(saveBtn).toBeTruthy();
+ saveBtn!.triggerEventHandler("click", null);
+
+ expect(datasetServiceStub.updateDatasetName).toHaveBeenCalledWith(5,
"typed-name");
+ });
+
+ it("renders every contributor row from the list", () => {
+ renderWith({
+ did: 5,
+ datasetContributors: [
+ { name: "Ada", email: "[email protected]", affiliation: "" } as Contributor,
+ { name: "Grace", email: "[email protected]", affiliation: "" } as
Contributor,
+ ],
+ });
+
+ const rendered = fixture.debugElement.nativeElement.textContent ?? "";
+ expect(rendered).toContain("Ada");
+ expect(rendered).toContain("Grace");
+ });
+
+ it("routes the settings switches' ngModelChange bindings to the service",
() => {
+ renderWith({
+ did: 5,
+ datasetIsPublic: false,
+ datasetIsDownloadable: true,
+ userDatasetAccessLevel: "WRITE",
+ isOwner: true, // the downloadable switch is [nzDisabled]="!isOwner"
+ });
+ openTab("Settings");
+
+ const switches = fixture.debugElement.queryAll(By.css("nz-switch"));
+ expect(switches.length).toBeGreaterThanOrEqual(2);
+
+ // fire the template's (ngModelChange) handlers rather than calling the
methods
+ switches[0].triggerEventHandler("ngModelChange", true);
+
expect(datasetServiceStub.updateDatasetPublicity).toHaveBeenCalledWith(5);
+
+ switches[1].triggerEventHandler("ngModelChange", false);
+
expect(datasetServiceStub.updateDatasetDownloadable).toHaveBeenCalledWith(5);
+ });
+ });
});