Unable to locate the Firebase UI widget on the Angular test environment page

I have been writing tests for my angular web application, specifically focusing on a page that includes a firebase UI element. The tests are split into two parts: one to verify that the page loads successfully and another to confirm that the firebaseUI component is loading correctly:

authentication.component.spec.ts

/*eslint-env jasmine*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { AuthenticationComponent } from './authentication.component';
import { FirebaseService } from '../services/firebase.service';

describe('AuthenticationComponent_Logged_Out', () => {
  let component: AuthenticationComponent;
  let fixture: ComponentFixture<AuthenticationComponent>;
  let service;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ AuthenticationComponent ],
      schemas: [ NO_ERRORS_SCHEMA ]
    }).compileComponents();
    service = TestBed.inject(FirebaseService);
    fixture = TestBed.createComponent(AuthenticationComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should render auth ui', () => {
    const compiled = fixture.nativeElement;
    expect(compiled.querySelector("#firebaseui_auth_container")).toBeTruthy();
  });

  afterEach(async () => {
    service.ui.reset();
  });
});

Using the following template file:

<script src="https://www.gstatic.com/firebasejs/ui/4.8.0/firebase-ui-auth.js"></script>
<link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/4.8.0/firebase-ui-auth.css" />

<div id="authentication-wrapper">
    <h1>Please sign in below to access your quotes!</h1>
    <div id="firebaseui_auth_container"></div>
    <div id="loader">Loading...</div>
</div>

With the accompanying class:

import { Component } from '@angular/core';
import { FirebaseService } from '../services/firebase.service';

@Component({
  selector: 'app-authentication',
  templateUrl: './authentication.component.html',
  styleUrls: ['./authentication.component.css']
})
export class AuthenticationComponent {

  constructor (private fbService: FirebaseService) {
    sessionStorage.removeItem('displayed_random');
    // If user logged in, redirect to feed
    if (fbService.currentUser) {
      window.location.href = "/feed";
    } else {
      this.fbService.instantiateUi();
    }
  }
}

The service responsible for loading the firebase UI is detailed in:

firebase.service.ts

import { Injectable } from '@angular/core';
import firebase from "firebase/app";
import * as firebaseui from "firebaseui";
import { config } from './config';
import 'firebase/database';
import 'firebase/auth';
firebase.initializeApp(config);

@Injectable({
  providedIn: 'root'
})
export class FirebaseService {
  currentUser: string;
  auth = firebase.auth();
  ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(this.auth);

  constructor () {
    const username = sessionStorage.getItem('username');
    if (username) {
      this.currentUser = username;
    }
  }

  signoutUser () {
    this.auth.signOut();
    this.currentUser = undefined;
    if (sessionStorage.getItem('username')) {
      sessionStorage.removeItem('username');
    }
  }

  getRef (path) {
    return firebase.database().ref(path);
  }

  instantiateUi () {
    this.ui.start("#firebaseui_auth_container", {
      callbacks: {
        signInSuccessWithAuthResult: (authResult) => {
          // Save username in storage
          sessionStorage.setItem('username', authResult.user.displayName);
          return true;
        },
        uiShown: () => {
          // The widget is rendered, hide the loader.
          document.getElementById('loader').style.display = 'none';
        }
      },
      // Will use popup for IDP Providers sign-in flow instead of the default, redirect.
      signInFlow: 'popup',
      signInSuccessUrl: 'feed',
      signInOptions: [
        {
          provider: firebase.auth.GoogleAuthProvider.PROVIDER_ID,
          customParameters: {
            prompt: 'select_account' // Forces account selection even when only one account is available.
          }
        },
        firebase.auth.EmailAuthProvider.PROVIDER_ID
      ]
    });
  }
}

In a standard development server environment using ng serve, the UI functions as intended. However, during testing, the UI fails to generate properly, resulting in issues with the firebaseui auth container not being found by the test framework. https://i.sstatic.net/0oo1c.png

https://i.sstatic.net/6s9wQ.png

I am currently troubleshooting why the test framework is unable to locate the firebaseui auth container. I have attempted multiple injections of the service and ensured the UI is reset after each test run, but so far without success. It seems that the variable compiled might be undefined for some reason, which is puzzling as this logic has worked seamlessly in my other tests.

Answer №1

After reviewing your provided files, I have devised several approaches to test your component effectively in this scenario. One key step is to mock the FirebaseService to ensure the reliability of this external dependency.

Our testing strategy will involve writing unit tests to safeguard the code integrity and detect any potential breakages caused by modifications in our source code.

Testing Objectives

We aim to create a minimum of four tests to evaluate the functionality of authentication.component.ts.

  • Verify successful creation of the component.
  • Ensure that the 'displayed_random' value in sessionStorage is empty.
  • Confirm redirection to specified location when currentUser is true.
  • Validate the invocation of the fbService function when currentUser is false.
constructor (private fbService: FirebaseService) {
    sessionStorage.removeItem('displayed_random');
    // Redirect to feed if user is logged in
    if (fbService.currentUser) {     
      window.location.href = "/feed";
    } else {
      this.fbService.instantiateUi();
    }
  }

Handling Current User Status

In your authentication.component.spec.ts file:

describe('AuthenticationComponent with current user', () => {
beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ AuthenticationComponent ],
      providers: [
        {
          provide: FirebaseService,
          useValue: {
             currentUser: true,
             instantiateUi: () => null
          }
        }
      ],
      schemas: [ NO_ERRORS_SCHEMA ]
    }).compileComponents();
    service = TestBed.inject(FirebaseService);
    fixture = TestBed.createComponent(AuthenticationComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
it('should create', () => {
  expect(component).toBeTruthy();
});
it('should have empty session storage for displayed_random at start', () => {
  expect(sessionStorage.getItem('displayed_random').toBeFalsy();
});
it('should update href value to redirect to feed', () => {
   expect(window.location.href).toBe('/feed');
});

Dealing with Absence of Current User

In your authentication.component.spec.ts:

describe('AuthenticationComponent without current user', () => {
beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ AuthenticationComponent ],
      providers: [
        {
          provide: FirebaseService,
          useValue: {
             currentUser: false,
             instantiateUi: () => null
          }
        }
      ],
      schemas: [ NO_ERRORS_SCHEMA ]
    }).compileComponents();
    service = TestBed.inject(FirebaseService);
    fixture = TestBed.createComponent(AuthenticationComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
it('should create', () => {
  expect(component).toBeTruthy();
});
it('should invoke instantiateUI when no current user exists', () => {
   const instantiateUiSpy = spyOn(service, 'instantiateUi');
   expect(instantiateUiSpy).toHaveBeenCalled();
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How to implement an instance method within a Typescript class for a Node.js application

I am encountering an issue with a callback function in my Typescript project. The problem arises when I try to implement the same functionality in a Node project using Typescript. It seems that when referencing 'this' in Node, it no longer points ...

Tips for implementing a setState function in React

Help needed! Trying to figure out how to type a setState in my application. Can someone guide me with this message? (property) setPageNumber: React.Dispatch<React.SetStateAction> The issue is: The object literal '{ movies: MovieData[]; pageNum ...

Broaden your interfaces by implementing multiple interfaces with Zod

Utilizing typescript, I am able to incorporate multiple interfaces interface Name { name: string } interface Age { age: number } interface People extends Name, Age { height: number } Is there a similar way to achieve this with Zod? What I attempted ...

I'm looking for a way to create a Redux thunk action creator that will return a promise. How

In my code, I have a primary thunk that is triggered by a button click. Within this thunk, I need to invoke another thunk and ensure it completes before proceeding. The second thunk returns a promise. Below is an excerpt of the code in question: export f ...

Load records as required

Currently, I am utilizing the ngx-owl-carousel-o carousel found here. lists : any; ... <owl-carousel-o [options]="customOptions"> <ng-template carouselSlide *ngFor="let list of lists"> </ng-template> < ...

Definition of Redux store state object

Recently, I started using Typescript in a React/Redux project, and I am facing some confusion regarding how type definitions should be incorporated into the application state. My goal is to make a specific piece of state accessible to a container component ...

Traversing through an array and populating a dropdown menu in Angular

Alright, here's the scoop on my dataset: people = [ { name: "Bob", age: "27", occupation: "Painter" }, { name: "Barry", age: "35", occupation: "Shop Assistant" }, { name: "Marvin", a ...

Why would someone use "extends an empty object" in TypeScript?

Explain the distinction between class Document<T extends {}>{} and class Document<T>{} Can you provide examples to illustrate the difference? ...

How to call a local method from catch in Angular 2+ without utilizing bind

I'm currently working on a situation where I need to call a method from a catch block in my authentication service. The purpose is to redirect the user to the login page and remove information from local storage if the server returns an error (e.g., 4 ...

Add a new item to an array in Angular 2 when a click event occurs

I'm trying to add a new list item (which comes from an API) when a button is pressed, but I'm not sure how to do it. Can anyone provide some guidance? Here's the code: <ul> <li *ngFor="let joke of jokes">{{joke.value}}</li> ...

Discover the geolocation data for post code 0821 exclusively in Australia using Google Maps Geocoding

I'm having trouble geocoding the Australian postcode 0821. It doesn't seem to reliably identify this postcode as being located within the Northern Territory, unlike 0820 and 0822 which work fine. Here's an example of what I'm doing: ...

Using the function goToPage() within the TabbedHeaderPager component

I am currently working on a project that involves using TabbedHeaderPager, and I need to change tabs programmatically. I have been attempting to use the function goToPage() but have run into difficulties accessing it. I have tried passing it as a prop an ...

Can you provide guidance on how to divide a series of dates and times into an array based

Given a startDate and an endDate, I am looking to split them into an array of time slots indicated by the duration provided. This is not a numerical pagination, but rather dividing a time range. In my TypeScript code: startDate: Date; endDate: Date; time ...

Issue with default selection persisting in Angular 9 when using ngModel

I'm encountering an issue where I am able to successfully autofill a text box based on the state from another component. However, when I attempt to add ngModel to the text box in order to capture the value upon form submission, the value is getting cl ...

WebSocket connection outbound from Docker container fails to establish

Running a TypeScript program on Docker that needs to open a Websocket connection to an external server can be a bit tricky. Here is the scenario: ----------------------- ------------------------------ | My Local Docker | ...

Utilizing a conditional ngIf statement in HTML or incorporating a variable within typescript for logical operations

When working with our application, we often need to display or hide a button based on specific logic. Where do you think it is best to define this logic and why? In HTML: *ngIf='logic goes here' //Or *ngIf='someBoolean' and in Type ...

Dynamic addition of script to <head> element in Angular is a common task for many developers

I have explored some examples that illustrate how to dynamically add a script with a URL in Angular However, I am interested in adding this type of content to the <head> <script> !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(! ...

The different types of property 'cacheLocation' do not match

I have been working on updating an old React app from JavaScript to Typescript gradually. I started by migrating the configuration file, but encountered an error when renaming it to .TS. Here is the error message: /Users/xx/yy/lulo/src/adalConfig.ts (13, ...

Exploring the DOM in JavaScript: Searching for the final ancestor containing a specific attribute

Check out this example of HTML code: <div id="main1" data-attribute="main"> <div id="section2" data-attribute="subsection"> <div id="nested3" data-attribute="sub-subsection"> </div> </div> </div> <div id= ...

Exploring data in Angular 8 and Firebase following the addition of a new item

Currently in my possession: Two Models: User.ts and Company.ts I aim to have each User linked to only one company, so that when a user registers, a new company is automatically registered on the firestore table. The following diagram provides a clear ...