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 f3f3fbe131 test(frontend): add unit tests for AdminGmailComponent
(#6576)
f3f3fbe131 is described below
commit f3f3fbe131891226c9b55261d991df41ed35054d
Author: Xinyuan Lin <[email protected]>
AuthorDate: Sun Jul 19 21:13:14 2026 -0700
test(frontend): add unit tests for AdminGmailComponent (#6576)
### What changes were proposed in this PR?
Adds a Vitest unit spec for `AdminGmailComponent`, which previously had
no test coverage. The 10 tests cover:
- reactive-form construction and every validator branch (empty,
malformed email, missing subject, missing content -> invalid; all filled
with a well-formed email -> valid).
- `getSenderEmail` success (sets `email`) and error (resets to undefined
+ logs) paths.
- `sendTestEmail` forwarding the form's subject/content/email to
`GmailService.sendEmail` in order.
`FormBuilder` stays real via `ReactiveFormsModule`; `GmailService` is
mocked with a controllable subject for both branches.
### Any related issues, documentation, discussions?
Closes #6573.
### How was this PR tested?
`npx ng test --watch=false --include='**/admin-gmail.component.spec.ts'`
-> 10/10 passing. `yarn format:ci` passes.
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.8 [1M context])
---
.../admin/gmail/admin-gmail.component.spec.ts | 168 +++++++++++++++++++++
1 file changed, 168 insertions(+)
diff --git
a/frontend/src/app/dashboard/component/admin/gmail/admin-gmail.component.spec.ts
b/frontend/src/app/dashboard/component/admin/gmail/admin-gmail.component.spec.ts
new file mode 100644
index 0000000000..7c24b30f10
--- /dev/null
+++
b/frontend/src/app/dashboard/component/admin/gmail/admin-gmail.component.spec.ts
@@ -0,0 +1,168 @@
+/**
+ * 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 { ComponentFixture, TestBed } from "@angular/core/testing";
+import { FormsModule, ReactiveFormsModule } from "@angular/forms";
+import { NoopAnimationsModule } from "@angular/platform-browser/animations";
+import { HttpClientTestingModule } from "@angular/common/http/testing";
+import { Subject } from "rxjs";
+import { AdminGmailComponent } from "./admin-gmail.component";
+import { GmailService } from "../../../../common/service/gmail/gmail.service";
+import { commonTestProviders } from "../../../../common/testing/test-utils";
+
+describe("AdminGmailComponent", () => {
+ let component: AdminGmailComponent;
+ let fixture: ComponentFixture<AdminGmailComponent>;
+ let senderEmailSubject: Subject<string>;
+ let getSenderEmailSpy: ReturnType<typeof vi.fn>;
+ let sendEmailSpy: ReturnType<typeof vi.fn>;
+
+ /** Create the component (runs ngOnInit) and render the template once. */
+ function createComponent(): void {
+ fixture = TestBed.createComponent(AdminGmailComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ }
+
+ beforeEach(async () => {
+ // Controllable GmailService double: getSenderEmail returns a subject we
drive
+ // per test (both next and error branches); sendEmail is fire-and-forget
so a
+ // plain vi.fn() is enough.
+ senderEmailSubject = new Subject<string>();
+ getSenderEmailSpy = vi.fn(() => senderEmailSubject.asObservable());
+ sendEmailSpy = vi.fn();
+ const gmailServiceStub = { getSenderEmail: getSenderEmailSpy, sendEmail:
sendEmailSpy };
+
+ await TestBed.configureTestingModule({
+ imports: [AdminGmailComponent, FormsModule, ReactiveFormsModule,
NoopAnimationsModule, HttpClientTestingModule],
+ providers: [{ provide: GmailService, useValue: gmailServiceStub },
...commonTestProviders],
+ }).compileComponents();
+ });
+
+ afterEach(() => {
+ fixture?.destroy();
+ document.querySelectorAll(".cdk-overlay-container").forEach(c =>
(c.innerHTML = ""));
+ vi.restoreAllMocks();
+ });
+
+ it("should create and render the reactive form template", () => {
+ createComponent();
+
+ expect(component).toBeTruthy();
+ const el = fixture.nativeElement as HTMLElement;
+ // The template binds each control; confirm the inputs rendered for .html
coverage.
+ expect(el.querySelector('input[formControlName="email"]')).toBeTruthy();
+ expect(el.querySelector('input[formControlName="subject"]')).toBeTruthy();
+
expect(el.querySelector('textarea[formControlName="content"]')).toBeTruthy();
+ });
+
+ describe("validateForm validation", () => {
+ beforeEach(() => createComponent());
+
+ it("builds the form with email/subject/content controls", () => {
+ expect(component.validateForm.contains("email")).toBe(true);
+ expect(component.validateForm.contains("subject")).toBe(true);
+ expect(component.validateForm.contains("content")).toBe(true);
+ });
+
+ it("is invalid when empty", () => {
+ expect(component.validateForm.valid).toBe(false);
+ });
+
+ it("is invalid when the email is malformed (all fields otherwise filled)",
() => {
+ component.validateForm.setValue({
+ email: "not-an-email",
+ subject: "Hello",
+ content: "Body",
+ });
+ expect(component.validateForm.valid).toBe(false);
+
expect(component.validateForm.get("email")?.hasError("email")).toBe(true);
+ });
+
+ it("is invalid when subject is missing", () => {
+ component.validateForm.setValue({
+ email: "[email protected]",
+ subject: null,
+ content: "Body",
+ });
+ expect(component.validateForm.valid).toBe(false);
+
expect(component.validateForm.get("subject")?.hasError("required")).toBe(true);
+ });
+
+ it("is invalid when content is missing", () => {
+ component.validateForm.setValue({
+ email: "[email protected]",
+ subject: "Hello",
+ content: null,
+ });
+ expect(component.validateForm.valid).toBe(false);
+
expect(component.validateForm.get("content")?.hasError("required")).toBe(true);
+ });
+
+ it("is valid when all three fields are filled with a well-formed email",
() => {
+ component.validateForm.setValue({
+ email: "[email protected]",
+ subject: "Hello",
+ content: "Body",
+ });
+ expect(component.validateForm.valid).toBe(true);
+ });
+ });
+
+ describe("getSenderEmail (called from ngOnInit)", () => {
+ it("subscribes on init and sets email to the emitted value", () => {
+ createComponent();
+ expect(getSenderEmailSpy).toHaveBeenCalledTimes(1);
+ expect(component.email).toBeUndefined();
+
+ senderEmailSubject.next("[email protected]");
+
+ expect(component.email).toBe("[email protected]");
+ });
+
+ it("resets email to undefined and logs when the sender lookup errors", ()
=> {
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
+ createComponent();
+ // Sentinel so the reset-to-undefined is observable, not a no-op default.
+ component.email = "[email protected]";
+ const err = new Error("lookup failed");
+
+ senderEmailSubject.error(err);
+
+ expect(component.email).toBeUndefined();
+ expect(logSpy).toHaveBeenCalledWith(err);
+ });
+ });
+
+ describe("sendTestEmail", () => {
+ it("calls GmailService.sendEmail with the form's subject, content and
email", () => {
+ createComponent();
+ component.validateForm.setValue({
+ email: "[email protected]",
+ subject: "Test Subject",
+ content: "Test Content",
+ });
+
+ component.sendTestEmail();
+
+ expect(sendEmailSpy).toHaveBeenCalledTimes(1);
+ expect(sendEmailSpy).toHaveBeenCalledWith("Test Subject", "Test
Content", "[email protected]");
+ });
+ });
+});