You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/07/15 00:03:57 UTC
(texera) 01/01: test(frontend): add unit test coverage for MarkdownDescriptionComponent (#6431)
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-6431-d561da8f3ca8b3ee6ce39b9a66320372d295f186
in repository https://gitbox.apache.org/repos/asf/texera.git
commit 54fa99485848197a9b80a781c6f740e065374734
Author: Meng Wang <me...@uci.edu>
AuthorDate: Tue Jul 14 17:03:35 2026 -0700
test(frontend): add unit test coverage for MarkdownDescriptionComponent (#6431)
### What changes were proposed in this PR?
Adds a behavior-focused Vitest spec for `MarkdownDescriptionComponent`
(`frontend/src/app/dashboard/component/user/markdown-description/markdown-description.component.ts`),
which previously had no spec (~46% coverage).
> Note: issue #6428 lists the path as
`workspace/component/markdown-description/`;
> the component actually lives under
`dashboard/component/user/markdown-description/`.
14 tests (TestBed + `detectChanges()`, `MarkdownService` stubbed,
`NZ_MODAL_DATA`
provided only for the modal cases) cover:
- `should create` and render the preview template.
- The `editable` input toggling the preview-mode Edit action (shown /
hidden).
- `ngOnInit` — inline case: starts in `preview`, seeds `editingContent`
from `description`; modal case: `editable = true`, `currentMode =
"edit"`, `description` pulled from modal data (and defaulted to `""`
when omitted).
- `ngOnChanges` — refreshes `editingContent` + re-renders on a non-first
`description` change in preview mode; leaves it untouched under the
edit-mode guard; ignores the first change.
- `enterEditMode` — switches to edit mode only when `editable`.
- `save` — emits `descriptionChange` with the edited content; returns to
`preview` inline but stays in `edit` when opened in a modal.
- `cancel` — restores `editingContent` from `description` and returns to
`preview`.
- `renderMarkdown` — renders non-empty input and clears on blank input
without parsing.
No production code was changed.
### Any related issues, documentation, discussions?
Closes #6428
### How was this PR tested?
New unit tests, run locally in `frontend/` (all green; the failure path
was
verified by deliberately breaking an assertion to confirm the suite goes
red):
```
ng test --watch=false --include src/app/dashboard/component/user/markdown-description/markdown-description.component.spec.ts
# Test Files 1 passed (1) | Tests 14 passed (14)
prettier --write <spec> # formatted
eslint <spec> # clean
```
`MarkdownService.parse` is stubbed and `NZ_MODAL_DATA` is injected via
the test
module, so the suite makes no network calls and needs no real markdown
config
(see `frontend/TESTING.md`).
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8 [1M context])
---------
Signed-off-by: Meng Wang <me...@uci.edu>
Co-authored-by: Copilot Autofix powered by AI <17...@users.noreply.github.com>
---
.../markdown-description.component.spec.ts | 237 +++++++++++++++++++++
1 file changed, 237 insertions(+)
diff --git a/frontend/src/app/dashboard/component/user/markdown-description/markdown-description.component.spec.ts b/frontend/src/app/dashboard/component/user/markdown-description/markdown-description.component.spec.ts
new file mode 100644
index 0000000000..d2f807cd97
--- /dev/null
+++ b/frontend/src/app/dashboard/component/user/markdown-description/markdown-description.component.spec.ts
@@ -0,0 +1,237 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Provider, SimpleChange } from "@angular/core";
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { NZ_MODAL_DATA } from "ng-zorro-antd/modal";
+import { MarkdownService } from "ngx-markdown";
+import { MarkdownDescriptionComponent } from "./markdown-description.component";
+
+describe("MarkdownDescriptionComponent", () => {
+ // Echo non-empty markdown as trivial HTML so `renderedDescription` is observable
+ // without pulling in the real (config-dependent) ngx-markdown parser.
+ const parse = vi.fn((text: string) => (text ? `<p>${text}</p>` : ""));
+
+ // Passing `modalData` provides NZ_MODAL_DATA (the "opened in a modal" case);
+ // omitting it leaves the optional injection null (the inline case).
+ async function createFixture(modalData?: {
+ description?: string;
+ }): Promise<ComponentFixture<MarkdownDescriptionComponent>> {
+ const providers: Provider[] = [{ provide: MarkdownService, useValue: { parse } }];
+ if (modalData !== undefined) {
+ providers.push({ provide: NZ_MODAL_DATA, useValue: modalData });
+ }
+ await TestBed.configureTestingModule({
+ imports: [MarkdownDescriptionComponent],
+ providers,
+ }).compileComponents();
+ return TestBed.createComponent(MarkdownDescriptionComponent);
+ }
+
+ // A non-first change of the `description` input.
+ const descriptionChange = (previous: string, current: string): { description: SimpleChange } => ({
+ description: new SimpleChange(previous, current, false),
+ });
+
+ beforeEach(() => parse.mockClear());
+
+ it("should create and render the preview template when not opened in a modal", async () => {
+ const fixture = await createFixture();
+ fixture.componentInstance.description = "hello";
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(fixture.componentInstance).toBeTruthy();
+ expect(fixture.nativeElement.querySelector(".preview-box")).toBeTruthy();
+ });
+
+ it("shows the Edit action in preview mode when editable", async () => {
+ const fixture = await createFixture();
+ fixture.componentInstance.editable = true;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.querySelector(".md-actions")).toBeTruthy();
+ });
+
+ it("hides the Edit action in preview mode when not editable", async () => {
+ const fixture = await createFixture();
+ fixture.componentInstance.editable = false;
+ fixture.detectChanges();
+ expect(fixture.nativeElement.querySelector(".md-actions")).toBeNull();
+ });
+
+ it("binds enableViewMore by toggling the preview collapsed class", async () => {
+ const fixture = await createFixture();
+ fixture.componentInstance.enableViewMore = true;
+ fixture.detectChanges();
+
+ const previewBox = fixture.nativeElement.querySelector(".preview-box") as HTMLElement;
+ expect(previewBox.classList.contains("collapsed")).toBe(true);
+
+ fixture.componentInstance.enableViewMore = false;
+ fixture.detectChanges();
+ expect(previewBox.classList.contains("collapsed")).toBe(false);
+ });
+ it("ngOnInit starts in preview mode and seeds editingContent from description (inline)", async () => {
+ const fixture = await createFixture();
+ const component = fixture.componentInstance;
+ component.description = "content";
+ fixture.detectChanges();
+
+ expect(component.currentMode).toBe("preview");
+ expect(component.editingContent).toBe("content");
+ expect(component.editable).toBe(false);
+ });
+
+ it("ngOnInit enters edit mode and pulls the description from modal data", async () => {
+ const fixture = await createFixture({ description: "from modal" });
+ const component = fixture.componentInstance;
+ fixture.detectChanges();
+
+ expect(component.editable).toBe(true);
+ expect(component.currentMode).toBe("edit");
+ expect(component.description).toBe("from modal");
+ expect(component.editingContent).toBe("from modal");
+ });
+
+ it("ngOnInit defaults the description to empty when modal data omits it", async () => {
+ const fixture = await createFixture({});
+ const component = fixture.componentInstance;
+ fixture.detectChanges();
+
+ expect(component.description).toBe("");
+ expect(component.currentMode).toBe("edit");
+ });
+
+ it("ngOnChanges refreshes editingContent from a new description while in preview mode", async () => {
+ const fixture = await createFixture();
+ const component = fixture.componentInstance;
+ component.description = "old";
+ fixture.detectChanges();
+ parse.mockClear();
+
+ component.description = "new";
+ component.ngOnChanges(descriptionChange("old", "new"));
+
+ expect(component.editingContent).toBe("new");
+ expect(parse).toHaveBeenCalledWith("new");
+ });
+
+ it("ngOnChanges leaves editingContent untouched while in edit mode (unsaved edits win)", async () => {
+ const fixture = await createFixture({ description: "orig" });
+ const component = fixture.componentInstance;
+ fixture.detectChanges(); // modal -> edit mode
+ component.editingContent = "user is typing";
+
+ component.description = "external change";
+ component.ngOnChanges(descriptionChange("orig", "external change"));
+
+ expect(component.editingContent).toBe("user is typing");
+ });
+
+ it("ngOnChanges ignores the first change", async () => {
+ const fixture = await createFixture();
+ const component = fixture.componentInstance;
+ component.description = "seed";
+ fixture.detectChanges();
+ component.editingContent = "seed";
+
+ component.ngOnChanges({ description: new SimpleChange(undefined, "seed", true) });
+
+ expect(component.editingContent).toBe("seed");
+ });
+
+ it("enterEditMode switches to edit mode only when editable", async () => {
+ const fixture = await createFixture();
+ const component = fixture.componentInstance;
+ component.description = "d";
+ fixture.detectChanges();
+
+ component.editable = false;
+ component.enterEditMode();
+ expect(component.currentMode).toBe("preview");
+
+ component.editable = true;
+ component.enterEditMode();
+ expect(component.currentMode).toBe("edit");
+ expect(component.editingContent).toBe("d");
+ });
+
+ it("save emits descriptionChange and returns to preview mode when inline", async () => {
+ const fixture = await createFixture();
+ const component = fixture.componentInstance;
+ component.description = "old";
+ component.editable = true;
+ fixture.detectChanges();
+ component.enterEditMode();
+ component.editingContent = "edited";
+
+ const emitted: string[] = [];
+ component.descriptionChange.subscribe(v => emitted.push(v));
+ component.save();
+
+ expect(emitted).toEqual(["edited"]);
+ expect(component.description).toBe("edited");
+ expect(component.currentMode).toBe("preview");
+ });
+
+ it("save stays in edit mode when opened in a modal", async () => {
+ const fixture = await createFixture({ description: "x" });
+ const component = fixture.componentInstance;
+ fixture.detectChanges(); // modal -> edit mode
+ component.editingContent = "y";
+
+ const emitted: string[] = [];
+ component.descriptionChange.subscribe(v => emitted.push(v));
+ component.save();
+
+ expect(emitted).toEqual(["y"]);
+ expect(component.currentMode).toBe("edit");
+ });
+
+ it("cancel restores editingContent from description and returns to preview", async () => {
+ const fixture = await createFixture();
+ const component = fixture.componentInstance;
+ component.description = "orig";
+ component.editable = true;
+ fixture.detectChanges();
+ component.enterEditMode();
+ component.editingContent = "unsaved";
+
+ component.cancel();
+
+ expect(component.editingContent).toBe("orig");
+ expect(component.currentMode).toBe("preview");
+ });
+
+ it("renderMarkdown renders non-empty input and clears on blank input without parsing", async () => {
+ const fixture = await createFixture();
+ const component = fixture.componentInstance;
+ fixture.detectChanges();
+
+ component.renderMarkdown("# Hi");
+ await fixture.whenStable();
+ expect(parse).toHaveBeenCalledWith("# Hi");
+ expect(component.renderedDescription).toBe("<p># Hi</p>");
+
+ parse.mockClear();
+ component.renderMarkdown(" ");
+ expect(parse).not.toHaveBeenCalled();
+ expect(component.renderedDescription).toBe("");
+ });
+});