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-1ff47715057a2c9e3a5ca94761a574f5d6687f63
in repository https://gitbox.apache.org/repos/asf/texera.git

commit 4eb3673851cd84e87f85d73acd0b384678aa22ab
Author: Meng Wang <[email protected]>
AuthorDate: Tue Jul 14 18:33:28 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 <[email protected]>
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
---
 .../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("");
+  });
+});

Reply via email to