Merge pull request #32 from giacomoferlaino/master

Added Ng2FittextDirective class unit tests
This commit is contained in:
Lorenzo Iovino 2020-03-16 12:28:51 +01:00 committed by GitHub
commit 35736e9af0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 294 additions and 63 deletions

20
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "attach",
"name": "Unit tests",
"address": "localhost",
"port": 9333,
"sourceMaps": true,
"webRoot": "${workspaceFolder}",
"pathMapping": {
"/_karma_webpack_": "${workspaceFolder}"
}
}
]
}

View file

@ -1,4 +1,5 @@
{ {
"prettier.configPath": "./src/.prettierrc", "prettier.configPath": "./src/.prettierrc",
"editor.formatOnSave": true "editor.formatOnSave": true,
"javascript.implicitProjectConfig.experimentalDecorators": true
} }

View file

@ -1,41 +1,45 @@
# ng2-fittext # ng2-fittext
An Angular2 directive written in pure typescript (and without jquery!), for autoscale the font size of an element to fit an upper level container. An Angular2 directive written in pure typescript (and without jquery!), to autoscale the font size of an element so that it fits an upper level container.
### Demo ### Demo
http://plnkr.co/edit/v0TQaYepV4E2Heur02j5?p=preview http://plnkr.co/edit/v0TQaYepV4E2Heur02j5?p=preview
### Installation ### Installation
Install the library Install the library
```sh ```sh
$ npm install --save ng2-fittext $ npm install --save ng2-fittext
``` ```
### Usage ### Usage
1) Declare it in your module
```sh 1. Declare it in your module
import {Ng2FittextModule} from "ng2-fittext"; ```sh
@NgModule({ import {Ng2FittextModule} from "ng2-fittext";
imports: [ @NgModule({
Ng2FittextModule imports: [
] Ng2FittextModule
}) ]
})
``` ```
2) Use it in your components 2. Use it in your components
```sh ```sh
import {Component} from '@angular/core'; import {Component} from '@angular/core';
@Component({ @Component({
selector: 'label', selector: 'label',
template: `<div #container> template: `<div #container>
<div [fittext]="true" [activateOnResize]="true" [container]="container">Bla bla bla...</div> <div [fittext]="true" [activateOnResize]="true" [container]="container">Bla bla bla...</div>
</div>` </div>`
}) })
export class LabelComponent {} export class LabelComponent {}
``` ```
### Examples ### Examples
Fit with the parent element (this works if you have a variable number of element between element and parent)
Fit to the parent element (this works if you have a variable number of elements between your element and its parent)
```sh ```sh
import {Component} from '@angular/core'; import {Component} from '@angular/core';
@ -61,6 +65,7 @@ import {Component} from '@angular/core';
export class InputBoxComponent {} export class InputBoxComponent {}
``` ```
**NEW! Support for maxFontSize!** **NEW! Support for maxFontSize!**
```sh ```sh
@ -74,46 +79,40 @@ import {Component} from '@angular/core';
export class InputBoxComponent {} export class InputBoxComponent {}
``` ```
Input Parameters:
Input Parameters: | Parameter | Description | Values |
| ------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------- |
| fittext | the directive selector | true/false |
| container | the container to fit (if not present it fits to the parent container by default) | ElementRef |
| activateOnResize | enable/disable the autofit in case of window resize | true or false (default false) |
| activateOnInputEvents | enable/disable the autofit in case of input box events (keydown, keyup etc..) | true or false (default false) |
| maxFontSize | maximum font size | number, default is 1000 |
| **!deprecated!** useMaxFontSize | use max font size if is true | deprecated! |
| minFontSize | minimum font size | number, default is 7 |
| modelToWatch | pass model to watch, when this model changes -> font size is automatically recalculated | any type of model |
| Parameter | Description | Values | Output Parameters:
| --- | --- | --- |
| fittext | is the selector of the directive | true/false
| container | the container to fit (if not present it fit default to parent container)| ElementRef
| activateOnResize | enable/disable the autofit in case of window resize | true or false (default false)
| activateOnInputEvents | enbale/disable the autofit in case of input box events (keydown, keyup etc..) | true or false (default false)
| maxFontSize | maximal font size | number, default is 1000
| **!deprecated!** useMaxFontSize | use max font size if is true | deprecated!
| minFontSize | minimal font size | number, default is 7
| modelToWatch | pass model to watch, when this model changes -> font size is automatically recalculated | any type of model
| Parameter | Description | Values |
Output Parameters: | --------------- | ----------------- | ------ |
| fontSizeChanged | current font size | string |
| Parameter | Description | Values |
| --- | --- | --- |
| fontSizeChanged | current font size | string
### Development ### Development
Want to contribute? Great! Want to contribute? Great!
Simply, clone the repository! Simply, clone the repository!
I created this library because I always spended too much time for solve this problem and because i didn't find nothing on the web (13/03/2017) that do this without jquery and easily integrable in angular2. I created this library because I always spent too much time to solve this problem and didn't find anything on the web (13/03/2017) that does this without jquery and that is also easily integrable in angular2.
For sure is not a good implementation, maybe is not the best way to do it, but, it do the job. For sure it is not the best implementation, maybe is not the best way to do it, but, it gets the job done.
### Todos ### Todos
- Write tests - Write tests
- Find a better algorithm to find the font-size who fits better the container - Find a better algorithm to find the font-size who fits better the container
License ## License
----
MIT MIT
**Lorenzo I.** **Lorenzo I.**

View file

@ -1,7 +1,7 @@
// Karma configuration file, see link for more information // Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html // https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) { module.exports = function(config) {
config.set({ config.set({
basePath: '', basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'], frameworks: ['jasmine', '@angular-devkit/build-angular'],
@ -10,22 +10,28 @@ module.exports = function (config) {
require('karma-chrome-launcher'), require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'), require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'), require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma') require('@angular-devkit/build-angular/plugins/karma'),
], ],
client: { client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser clearContext: false, // leave Jasmine Spec Runner output visible in browser
}, },
coverageIstanbulReporter: { coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../coverage'), dir: require('path').join(__dirname, '../coverage'),
reports: ['html', 'lcovonly'], reports: ['html', 'lcovonly'],
fixWebpackSourcePaths: true fixWebpackSourcePaths: true,
}, },
reporters: ['progress', 'kjhtml'], reporters: ['progress', 'kjhtml'],
port: 9876, port: 9876,
colors: true, colors: true,
logLevel: config.LOG_INFO, logLevel: config.LOG_INFO,
autoWatch: true, autoWatch: true,
browsers: ['Chrome'], browsers: ['Chrome', 'ChromeHeadlessDebug'],
singleRun: false customLaunchers: {
ChromeHeadlessDebug: {
base: 'ChromeHeadless',
flags: ['--remote-debugging-port=9333'],
},
},
singleRun: false,
}); });
}; };

View file

@ -6,6 +6,7 @@
"start": "./node_modules/.bin/ng serve", "start": "./node_modules/.bin/ng serve",
"build": "./node_modules/.bin/ng build --aot --prod", "build": "./node_modules/.bin/ng build --aot --prod",
"test": "./node_modules/.bin/ng test", "test": "./node_modules/.bin/ng test",
"test:headless": "./node_modules/.bin/ng test --browsers=ChromeHeadlessDebug",
"lint": "./node_modules/.bin/ng lint", "lint": "./node_modules/.bin/ng lint",
"e2e": "./node_modules/.bin/ng e2e", "e2e": "./node_modules/.bin/ng e2e",
"pack": "./node_modules/.bin/ng-packagr -p ./src/lib/package.json" "pack": "./node_modules/.bin/ng-packagr -p ./src/lib/package.json"

View file

@ -29,7 +29,7 @@ export class Ng2FittextDirective
@Input('modelToWatch') modelToWatch: any; @Input('modelToWatch') modelToWatch: any;
@Output() fontSizeChanged = new EventEmitter(); @Output() fontSizeChanged: EventEmitter<any> = new EventEmitter();
private fontSize = 1000; private fontSize = 1000;
private speed = 1.05; private speed = 1.05;
@ -37,29 +37,32 @@ export class Ng2FittextDirective
constructor(public el: ElementRef, public renderer: Renderer2) {} constructor(public el: ElementRef, public renderer: Renderer2) {}
setFontSize(fontSize: number) { setFontSize(fontSize: number): void {
if (this.isVisible() && !this.done) { if (this.isVisible() && !this.isDone()) {
if (fontSize < this.minFontSize) { if (fontSize < this.minFontSize) {
fontSize = this.minFontSize; fontSize = this.minFontSize;
} }
if (fontSize > this.maxFontSize) { if (fontSize > this.maxFontSize) {
fontSize = this.maxFontSize; fontSize = this.maxFontSize;
} }
this.fontSize = fontSize; this.fontSize = fontSize;
this.fontSizeChanged.emit(fontSize); this.fontSizeChanged.emit(fontSize);
return this.el.nativeElement.style.setProperty( this.el.nativeElement.style.setProperty(
'font-size', 'font-size',
fontSize.toString() + 'px' fontSize.toString() + 'px'
); );
} }
} }
calculateFontSize(fontSize: number, speed: number) { getFontSize(): number {
return this.fontSize;
}
calculateFontSize(fontSize: number, speed: number): number {
return Math.floor(fontSize / speed); return Math.floor(fontSize / speed);
} }
checkOverflow(parent: any, children: any) { checkOverflow(parent: any, children: any): boolean {
const overflowX = children.scrollWidth - parent.clientWidth; const overflowX = children.scrollWidth - parent.clientWidth;
const overflowY = children.clientHeight - parent.clientHeight; const overflowY = children.clientHeight - parent.clientHeight;
return overflowX > 1 || overflowY > 1; return overflowX > 1 || overflowY > 1;
@ -95,7 +98,7 @@ export class Ng2FittextDirective
} }
ngAfterViewInit() { ngAfterViewInit() {
if (this.isVisible() && !this.done) { if (this.isVisible() && !this.isDone()) {
if (this.fittext) { if (this.fittext) {
const overflow = this.container const overflow = this.container
? this.checkOverflow(this.container, this.el.nativeElement) ? this.checkOverflow(this.container, this.el.nativeElement)
@ -134,7 +137,7 @@ export class Ng2FittextDirective
} }
} }
private getStartFontSizeFromHeight(): number { getStartFontSizeFromHeight(): number {
return this.container return this.container
? this.container.clientHeight ? this.container.clientHeight
: this.el.nativeElement.parentElement.clientHeight; : this.el.nativeElement.parentElement.clientHeight;
@ -146,7 +149,11 @@ export class Ng2FittextDirective
: this.el.nativeElement.parentElement.clientWidth; : this.el.nativeElement.parentElement.clientWidth;
} }
private isVisible(): boolean { isDone(): boolean {
return this.done;
}
isVisible(): boolean {
return this.getStartFontSizeFromHeight() > 0; return this.getStartFontSizeFromHeight() > 0;
} }
} }

View file

@ -0,0 +1,197 @@
import { Ng2FittextDirective } from '../ng2-fittext.directive';
import { Renderer2, ElementRef } from '@angular/core';
describe('Class: Ng2FittextDirective', () => {
let ng2FittextDirective: Ng2FittextDirective;
let elMock: ElementRef;
let rendererMock: Renderer2;
beforeEach(() => {
elMock = {} as ElementRef;
rendererMock = {} as Renderer2;
ng2FittextDirective = new Ng2FittextDirective(elMock, rendererMock);
});
describe('Method: setFontSize', () => {
let newFontSize: number;
let isVisibleSpy: jasmine.Spy;
let isDoneSpy: jasmine.Spy;
beforeEach(() => {
newFontSize = 100;
isVisibleSpy = spyOn(ng2FittextDirective, 'isVisible').and.returnValue(
true
);
isDoneSpy = spyOn(ng2FittextDirective, 'isDone').and.returnValue(false);
elMock.nativeElement = {
style: {
setProperty: () => {},
},
};
});
it('Should not change the font size if the element is not visible', () => {
isVisibleSpy.and.returnValue(false);
const previousFontSize: number = ng2FittextDirective.getFontSize();
ng2FittextDirective.setFontSize(newFontSize);
expect(ng2FittextDirective.getFontSize()).toEqual(previousFontSize);
});
it('Should not change the font size if the fitting operation is done', () => {
isDoneSpy.and.returnValue(true);
const previousFontSize: number = ng2FittextDirective.getFontSize();
ng2FittextDirective.setFontSize(newFontSize);
expect(ng2FittextDirective.getFontSize()).toEqual(previousFontSize);
});
it('Should use the minFontSize property value if the specified font size is smaller', () => {
const minFontSize: number = ng2FittextDirective.minFontSize;
newFontSize = 5;
ng2FittextDirective.setFontSize(newFontSize);
const currentFontSize: number = ng2FittextDirective.getFontSize();
expect(currentFontSize).toEqual(minFontSize);
});
it('Should use the maxFontSize property value if the specified font size is bigger', () => {
const maxFontSize: number = ng2FittextDirective.maxFontSize;
newFontSize = 1001;
ng2FittextDirective.setFontSize(newFontSize);
const currentFontSize: number = ng2FittextDirective.getFontSize();
expect(currentFontSize).toEqual(maxFontSize);
});
it('Should set a new fontSize value', () => {
newFontSize = 500;
ng2FittextDirective.setFontSize(newFontSize);
const currentFontSize: number = ng2FittextDirective.getFontSize();
expect(currentFontSize).toEqual(newFontSize);
});
it('Should emit the font size change', () => {
newFontSize = 500;
spyOn(ng2FittextDirective.fontSizeChanged, 'emit');
ng2FittextDirective.setFontSize(newFontSize);
expect(ng2FittextDirective.fontSizeChanged.emit).toHaveBeenCalledWith(
newFontSize
);
});
it('Should update the nativeElement with the new font size', () => {
newFontSize = 500;
spyOn(ng2FittextDirective.el.nativeElement.style, 'setProperty');
ng2FittextDirective.setFontSize(newFontSize);
expect(
ng2FittextDirective.el.nativeElement.style.setProperty
).toHaveBeenCalledWith('font-size', `${newFontSize}px`);
});
});
describe('Method: getFontSize', () => {
it('Should return the current font size', () => {
expect(ng2FittextDirective.getFontSize()).toEqual(1000);
});
});
describe('Method: calculateFontSize', () => {
it('Should return the font size rounded down', () => {
expect(ng2FittextDirective.calculateFontSize(10, 3)).toEqual(3);
expect(ng2FittextDirective.calculateFontSize(9, 3)).toEqual(3);
expect(ng2FittextDirective.calculateFontSize(8, 3)).toEqual(2);
});
});
describe('Method: checkOverflow', () => {
let parentElementMock: any;
let childrenElementMock: any;
beforeEach(() => {
parentElementMock = {
clientWidth: 0,
clientHeight: 0,
};
childrenElementMock = {
scrollWidth: 0,
clientHeight: 0,
};
});
it('Should return false if no overflow is present', () => {
expect(
ng2FittextDirective.checkOverflow(
parentElementMock,
childrenElementMock
)
).toBe(false);
});
it('Should return true if x axis has overflow', () => {
childrenElementMock.scrollWidth = 2;
expect(
ng2FittextDirective.checkOverflow(
parentElementMock,
childrenElementMock
)
).toBe(true);
});
it('Should return true if y axis has overflow', () => {
childrenElementMock.clientHeight = 2;
expect(
ng2FittextDirective.checkOverflow(
parentElementMock,
childrenElementMock
)
).toBe(true);
});
});
describe('Method: getStartFontSizeFromHeight', () => {
it('Should return the container clientHeight value if the container is present', () => {
const containerClientHeight = 10;
ng2FittextDirective.container = {
clientHeight: containerClientHeight,
} as HTMLElement;
expect(ng2FittextDirective.getStartFontSizeFromHeight()).toEqual(
containerClientHeight
);
});
it('Should return the parentElement clientHeight value if no container is present', () => {
const parentlientHeight = 11;
elMock.nativeElement = {
parentElement: {
clientHeight: parentlientHeight,
},
} as HTMLElement;
expect(ng2FittextDirective.getStartFontSizeFromHeight()).toEqual(
parentlientHeight
);
});
});
describe('Method: isDone', () => {
it('Should return the done property value', () => {
const defaultDoneValue = false;
expect(ng2FittextDirective.isDone()).toBe(defaultDoneValue);
});
});
describe('Method: isVisible', () => {
it('Should return the true if getStartFontSizeFromHeight() is greater than zero', () => {
spyOn(ng2FittextDirective, 'getStartFontSizeFromHeight').and.returnValue(
1
);
expect(ng2FittextDirective.isVisible()).toBe(true);
});
it('Should return the false if getStartFontSizeFromHeight() is smaller or equal to zero', () => {
const spy = spyOn(
ng2FittextDirective,
'getStartFontSizeFromHeight'
).and.returnValue(0);
expect(ng2FittextDirective.isVisible()).toBe(false);
spy.and.returnValue(-1);
expect(ng2FittextDirective.isVisible()).toBe(false);
});
});
});