Skip to content

Commit

Permalink
Feat: Allow Configuring the testing module to not throw errors when a…
Browse files Browse the repository at this point in the history
…dding icons

The FontAwesomeTestingModule is now configurable via a `forRoot` method.
The configuration object passed to it configures how the mock icon library
should behave when adding icons to it: ignore them, warn about them or throw
an error.

Fix #440
#440
  • Loading branch information
bleistift-zwei authored and devoto13 committed May 9, 2024
1 parent dc9dcca commit 14c4812
Show file tree
Hide file tree
Showing 5 changed files with 297 additions and 35 deletions.
44 changes: 44 additions & 0 deletions testing/src/TestingModuleConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { InjectionToken } from "@angular/core"

/**
* The configuration options that can be passed to the FontAwesomeTestingModule.
*/
// This interface is user-facing and will be converted to the
// _FontAwesomeTestingModuleInternalConfig for internal use by the module.
export interface FontAwesomeTestingModuleConfig extends Partial<_FontAwesomeTestingModuleInternalConfig> { }

/**
* The internal configuration for the FontAwesomeTestingModule.
* This interface is private. Conforming objects should be constructed by the
* _getFontAwesomeTestingModuleInternalConfig() function.
*/
export interface _FontAwesomeTestingModuleInternalConfig {
/**
* What to do when `addIcons()` is invoked on an IconLibrary provided by the FontAwesomeTestingModule.
*
* Possible values are:
* - `'logWarning'`: Writes a warning to the console.
* - `'throwError'`: Throws an error
* - `'noop'`: Does nothing
*
* Note that in any case the icon will not be added to the library.
*/
whenAddingIcons: 'logWarning' | 'throwError' | 'noop'
}

export const FontAwesomeTestingModuleConfigInjectionToken = new InjectionToken<_FontAwesomeTestingModuleInternalConfig>('FontAwesomeTestingModuleInternalConfig')

/**
* The default values used for configuration if the user passes no configuration,
* or an incomplete one.
*/
const DEFAULT_CONFIG = Object.freeze({
// The default value maintains compatibility with versions <= 0.14.1
whenAddingIcons: 'throwError'
})

export function _getFontAwesomeTestingModuleInternalConfig(publicConfig: FontAwesomeTestingModuleConfig = {}): _FontAwesomeTestingModuleInternalConfig {
return {
whenAddingIcons: publicConfig.whenAddingIcons ?? DEFAULT_CONFIG.whenAddingIcons
}
}
38 changes: 35 additions & 3 deletions testing/src/icon/mock-icon-library.service.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,54 @@
import { Injectable } from '@angular/core';
import { Inject, Injectable, Optional } from '@angular/core';
import { FaIconLibraryInterface, IconDefinition, IconName, IconPrefix } from '@fortawesome/angular-fontawesome';
import {
FontAwesomeTestingModuleConfigInjectionToken,
_FontAwesomeTestingModuleInternalConfig,
_getFontAwesomeTestingModuleInternalConfig
} from '../TestingModuleConfig';

export const dummyIcon: IconDefinition = {
prefix: 'fad',
iconName: 'dummy',
icon: [512, 512, [], '', 'M50 50 H462 V462 H50 Z'],
};

export const ADD_ICON_MESSAGE = 'Attempt to add an icon to the MockFaIconLibrary.';

@Injectable({
providedIn: 'root',
})
export class MockFaIconLibrary implements FaIconLibraryInterface {

private config: _FontAwesomeTestingModuleInternalConfig

// The configuration object is optional in order to maintain backwards compatibility with versions <= 0.14.1.
// If the module is unconfigured (that is, FontAwesomeTestingModule.forRoot() has never been called),
// then the dependency injection will provide `null`.
//
// We could alternatively provide a default configuration in the `providers` array of the `NgModule` decorator,
// but that would break the use case of injecting a service directly without providing a configuration,
// as is done in testing/src/icon/mock-icon-library.service.spec.ts
constructor(
@Inject(FontAwesomeTestingModuleConfigInjectionToken) @Optional() config: _FontAwesomeTestingModuleInternalConfig
) {
this.config = config ?? _getFontAwesomeTestingModuleInternalConfig()
}
addIcons() {
throw new Error('Attempt to add an icon to the MockFaIconLibrary.');
if (this.config.whenAddingIcons === 'throwError') {
throw new Error(ADD_ICON_MESSAGE);
}
if (this.config.whenAddingIcons === 'logWarning') {
console.warn(ADD_ICON_MESSAGE)
}
}

addIconPacks() {
throw new Error('Attempt to add an icon pack to the MockFaIconLibrary.');
if (this.config.whenAddingIcons === 'throwError') {
throw new Error(ADD_ICON_MESSAGE);
}
if (this.config.whenAddingIcons === 'logWarning') {
console.warn(ADD_ICON_MESSAGE)
}
}

getIconDefinition(prefix: IconPrefix, name: IconName): IconDefinition {
Expand Down
3 changes: 2 additions & 1 deletion testing/src/public_api.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { FontAwesomeTestingModule } from './testing.module';
export { MockFaIconLibrary } from './icon/mock-icon-library.service'
export { MockFaIconLibrary } from './icon/mock-icon-library.service'
export { FontAwesomeTestingModuleConfig } from './TestingModuleConfig'
29 changes: 27 additions & 2 deletions testing/src/testing.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
import { NgModule } from '@angular/core';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { FaIconLibrary, FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { MockFaIconLibrary } from './icon/mock-icon-library.service';
import {
FontAwesomeTestingModuleConfig,
FontAwesomeTestingModuleConfigInjectionToken,
_getFontAwesomeTestingModuleInternalConfig,
} from './TestingModuleConfig';

@NgModule({
exports: [FontAwesomeModule],
providers: [{ provide: FaIconLibrary, useExisting: MockFaIconLibrary }],
})
export class FontAwesomeTestingModule {}
export class FontAwesomeTestingModule {
/**
* Use this method to configure the module’s behaviour when trying to add icons
* and icon packs to the mock icon library.
*/
public static forRoot(config: FontAwesomeTestingModuleConfig = {}): ModuleWithProviders<FontAwesomeTestingModule> {
return {
ngModule: FontAwesomeTestingModule,
providers: [
{
provide: FaIconLibrary,
useExisting: MockFaIconLibrary,
},
{
provide: FontAwesomeTestingModuleConfigInjectionToken,
useValue: _getFontAwesomeTestingModuleInternalConfig(config),
}
]
}
}
}
218 changes: 189 additions & 29 deletions testing/test/integration/module-test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faUser } from '@fortawesome/free-solid-svg-icons';
import { ADD_ICON_MESSAGE } from 'testing/src/icon/mock-icon-library.service';
import { FontAwesomeTestingModule } from 'testing/src/public_api';

@Component({
Expand All @@ -11,33 +12,192 @@ import { FontAwesomeTestingModule } from 'testing/src/public_api';
class HostComponent {}

describe('Using the `FontAwesomeTestingModule', () => {
let component: HostComponent;
let fixture: ComponentFixture<HostComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [FontAwesomeTestingModule],
declarations: [HostComponent],
});
});

beforeEach(() => {
fixture = TestBed.createComponent(HostComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should allow you to import the module without errors', () => {
expect(component).toBeTruthy();
});

it('should throw on attempt to add an icon to the mocked icon library', () => {
const service = TestBed.inject(FaIconLibrary);
expect(() => service.addIcons(faUser)).toThrow(new Error('Attempt to add an icon to the MockFaIconLibrary.'));
});

it('should throw on attempt to add an icon pack to the mocked icon library', () => {
const service = TestBed.inject(FaIconLibrary);
expect(() => service.addIcons(faUser)).toThrow(new Error('Attempt to add an icon to the MockFaIconLibrary.'));
});

describe('Providing no configuration', () => {
// This describe block asserts that the behaviour of versions <= 0.14.1 is maintained

let component: HostComponent;
let fixture: ComponentFixture<HostComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [FontAwesomeTestingModule],
declarations: [HostComponent],
});

fixture = TestBed.createComponent(HostComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should allow you to import the module without errors', () => {
expect(component).toBeTruthy();
});

it('should throw on attempt to add an icon to the mocked icon library', () => {
const service = TestBed.inject(FaIconLibrary);
expect(() => service.addIcons(faUser)).toThrow(new Error(ADD_ICON_MESSAGE));
});

it('should throw on attempt to add an icon pack to the mocked icon library', () => {
const service = TestBed.inject(FaIconLibrary);
expect(() => service.addIcons(faUser)).toThrow(new Error(ADD_ICON_MESSAGE));
});
})

describe('Providing an empty configuration object', () => {
// This describe block asserts that a partial configuration object
// is correctly filled up to the ‘full’ internal object.
// The used configuration should mimick the default values for ‘no configuration’.

let component: HostComponent;
let fixture: ComponentFixture<HostComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [FontAwesomeTestingModule.forRoot({})],
declarations: [HostComponent],
});
});

beforeEach(() => {
fixture = TestBed.createComponent(HostComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should allow you to import the module without errors', () => {
expect(component).toBeTruthy();
});

it('should throw on attempt to add an icon to the mocked icon library', () => {
const service = TestBed.inject(FaIconLibrary);
expect(() => service.addIcons(faUser)).toThrow(new Error(ADD_ICON_MESSAGE));
});

it('should throw on attempt to add an icon pack to the mocked icon library', () => {
const service = TestBed.inject(FaIconLibrary);
expect(() => service.addIcons(faUser)).toThrow(new Error(ADD_ICON_MESSAGE));
});
})


describe('Providing {addIcons: "throwError"}', () => {
// This describe block asserts that feature request
// https://github.com/FortAwesome/angular-fontawesome/issues/440
// is implemented correctly.

let component: HostComponent;
let fixture: ComponentFixture<HostComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [FontAwesomeTestingModule.forRoot({whenAddingIcons: 'throwError'})],
declarations: [HostComponent],
});
});

beforeEach(() => {
fixture = TestBed.createComponent(HostComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should allow you to import the module without errors', () => {
expect(component).toBeTruthy();
});

it('should throw on attempt to add an icon to the mocked icon library', () => {
const service = TestBed.inject(FaIconLibrary);
expect(() => service.addIcons(faUser)).toThrow(new Error('Attempt to add an icon to the MockFaIconLibrary.'));
});

it('should throw on attempt to add an icon pack to the mocked icon library', () => {
const service = TestBed.inject(FaIconLibrary);
expect(() => service.addIcons(faUser)).toThrow(new Error('Attempt to add an icon to the MockFaIconLibrary.'));
});
})


describe('Providing {addIcons: "logWarning"}', () => {
// This describe block asserts that feature request
// https://github.com/FortAwesome/angular-fontawesome/issues/440
// is implemented correctly.

let component: HostComponent;
let fixture: ComponentFixture<HostComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [FontAwesomeTestingModule.forRoot({whenAddingIcons: 'logWarning'})],
declarations: [HostComponent],
});
});

beforeEach(() => {
fixture = TestBed.createComponent(HostComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should allow you to import the module without errors', () => {
expect(component).toBeTruthy();
});

it('should call console.warn on attempt to add an icon to the mocked icon library', () => {
const service = TestBed.inject(FaIconLibrary);
spyOn(console, 'warn')
expect(() => service.addIcons(faUser)).not.toThrow();
expect(console.warn).toHaveBeenCalledOnceWith(ADD_ICON_MESSAGE)
});

it('should call console.warn on attempt to add an icon pack to the mocked icon library', () => {
const service = TestBed.inject(FaIconLibrary);
spyOn(console, 'warn')
expect(() => service.addIcons(faUser)).not.toThrow();
expect(console.warn).toHaveBeenCalledOnceWith(ADD_ICON_MESSAGE)
});
})



describe('Providing {addIcons: "noop"}', () => {
// This describe block asserts that feature request
// https://github.com/FortAwesome/angular-fontawesome/issues/440
// is implemented correctly.

let component: HostComponent;
let fixture: ComponentFixture<HostComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [FontAwesomeTestingModule.forRoot({whenAddingIcons: 'noop'})],
declarations: [HostComponent],
});
});

beforeEach(() => {
fixture = TestBed.createComponent(HostComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should allow you to import the module without errors', () => {
expect(component).toBeTruthy();
});

it('should ignore attempts to add an icon to the mocked icon library', () => {
const service = TestBed.inject(FaIconLibrary);
spyOn(console, 'warn')
expect(() => service.addIcons(faUser)).not.toThrow();
expect(console.warn).not.toHaveBeenCalledOnceWith(ADD_ICON_MESSAGE)
});

it('should ignore attempts to add an icon pack to the mocked icon library', () => {
const service = TestBed.inject(FaIconLibrary);
spyOn(console, 'warn')
expect(() => service.addIcons(faUser)).not.toThrow();
expect(console.warn).not.toHaveBeenCalledOnceWith(ADD_ICON_MESSAGE)
});
})
});

0 comments on commit 14c4812

Please sign in to comment.