Skip to content

Commit 43e19f9

Browse files
committed
feat: 🎸 add scheamtics to generate an icon library
1 parent 286fa03 commit 43e19f9

13 files changed

+399
-14
lines changed

package-lock.json

Lines changed: 99 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
},
3535
"dependencies": {
3636
"@angular-devkit/core": "^10.1.6",
37-
"@angular-devkit/schematics": "^10.1.6",
37+
"@angular-devkit/schematics": "^10.2.1",
38+
"@schematics/angular": "^11.0.6",
3839
"svg-to-ts": "^5.7.0",
3940
"typescript": "~4.0.2"
4041
},

schematics/collection.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
"description": "Add svg-to-ts to a project",
66
"factory": "./ng-add/index#ngAdd",
77
"aliases": ["install"]
8+
},
9+
"ng-generate-icon-lib": {
10+
"description": "Scaffold Angular icon library code in your project",
11+
"factory": "./ng-generate-icon-lib/index#generateIconLibrary",
12+
"schema": "./ng-generate-icon-lib/schema.json"
813
}
914
}
1015
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {<%= capitalize(camelize(name)) %>Registry} from './<%= dasherize(name) %>-registry.service';
2+
3+
describe('<%= capitalize(camelize(name)) %>Registry', () => {
4+
let sut: <%=capitalize(camelize(name)) %>Registry;
5+
6+
beforeEach(() => (sut = new <%=capitalize(camelize(name)) %>Registry()));
7+
8+
it('should add icons to the registry', () => {
9+
const iconName = 'someicon' as any;
10+
const iconData = 'some data' as any;
11+
const someIcon = { name: iconName, data: iconData };
12+
13+
sut.registerIcons([someIcon]);
14+
expect(sut.getIcon(iconName)).toEqual(iconData);
15+
});
16+
17+
it('should add multiple icons to the registry', () => {
18+
const iconOneName = 'barIcon' as any;
19+
const iconTwoName = 'fooIcon' as any;
20+
const iconOneData = 'iconOneData';
21+
const iconTwoData = 'iconTwoData';
22+
const iconOne = { name: iconOneName, data: iconOneData };
23+
const iconTwo = { name: iconTwoName, data: iconTwoData };
24+
25+
sut.registerIcons([iconOne, iconTwo]);
26+
expect(sut.getIcon(iconOneName)).toEqual(iconOneData);
27+
expect(sut.getIcon(iconTwoName)).toEqual(iconTwoData);
28+
});
29+
30+
it('should print a warning if an icon is not in the registry', () => {
31+
spyOn(console, 'warn');
32+
const iconName = 'someIcon' as any;
33+
sut.getIcon(iconName);
34+
expect(console.warn).toHaveBeenCalledWith(
35+
`👀 we could not find the Icon with the name ${iconName}, did you add it to the Icon registry?`
36+
);
37+
});
38+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Injectable } from '@angular/core';
2+
3+
import { <%= iconType %>, <%= iconInterface %>} from '<%= iconImportPath %>';
4+
5+
@Injectable({
6+
providedIn: 'any'
7+
})
8+
export class <%= capitalize(camelize(name)) %>Registry {
9+
private registry = new Map<<%= iconType %>, string>();
10+
11+
public registerIcons(icons: <%= iconInterface %>[]): void {
12+
icons.forEach((icon: any) => this.registry.set(icon.name, icon.data));
13+
}
14+
15+
public getIcon(iconName: <%= iconType %>): string | undefined {
16+
if (!this.registry.has(iconName)) {
17+
console.warn(
18+
`👀 we could not find the Icon with the name ${iconName}, did you add it to the Icon registry?`
19+
);
20+
}
21+
return this.registry.get(iconName);
22+
}
23+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {<%= capitalize(camelize(name)) %>Component} from './<%= dasherize(name) %>.component';
2+
3+
describe('<%= capitalize(camelize(name)) %>Component', () => {
4+
5+
let sut;
6+
7+
const elementMock = {
8+
nativeElement: {
9+
removeChild: jasmine.createSpy(),
10+
appendChild: jasmine.createSpy()
11+
}
12+
};
13+
14+
const iconRegistryMock = {
15+
getIcon: jasmine.createSpy()
16+
} as any;
17+
18+
beforeEach(() => sut = new <%= capitalize(camelize(name)) %>Component(elementMock, iconRegistryMock, document));
19+
20+
it('should append an SVG element to the ElementRef', () => {
21+
const iconName = 'my-awesome-icon';
22+
const svgData = '<svg>some svg content</svg>';
23+
const div = document.createElement('DIV');
24+
div.innerHTML = svgData;
25+
26+
iconRegistryMock.getIcon.and.callFake((name) => name === iconName ? svgData : null);
27+
28+
sut.name = iconName;
29+
expect(elementMock.nativeElement.appendChild).toHaveBeenCalledWith(div.querySelector('svg'));
30+
});
31+
32+
it('should remove the previous element if we want to append a new one', () => {
33+
const iconName = 'my-awesome-icon';
34+
const svgData = '<svg>some svg content</svg>';
35+
const div = document.createElement('DIV');
36+
div.innerHTML = svgData;
37+
38+
iconRegistryMock.getIcon.and.callFake((name) => name === iconName ? svgData : null);
39+
40+
sut.name = iconName;
41+
expect(elementMock.nativeElement.appendChild).toHaveBeenCalledWith(div.querySelector('svg'));
42+
43+
sut.name = 'anotherIcon';
44+
expect(elementMock.nativeElement.removeChild).toHaveBeenCalled();
45+
});
46+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {ChangeDetectionStrategy, Component, ElementRef, Inject, Input, Optional} from '@angular/core';
2+
import {DOCUMENT} from '@angular/common';
3+
import {<%= iconInterface %>} from '<%= iconImportPath %>';
4+
5+
import {<%= capitalize(camelize(name)) %>Registry} from './<%= dasherize(name) %>-registry.service';
6+
7+
@Component({
8+
selector: '<%= dasherize(name) %>',
9+
template: `
10+
<ng-content></ng-content>
11+
`,
12+
styles: [':host::ng-deep svg{width: <%= defaultIconSize %>px; height: <%= defaultIconSize %>px}'],
13+
changeDetection: ChangeDetectionStrategy.OnPush
14+
})
15+
export class <%= capitalize(camelize(name)) %>Component {
16+
private svgIcon: SVGElement;
17+
18+
@Input()
19+
set name(iconName: <%= iconInterface %>) {
20+
if (this.svgIcon) {
21+
this.element.nativeElement.removeChild(this.svgIcon);
22+
}
23+
const svgData = this.<%= camelize(name) %>Registry.getIcon(iconName);
24+
this.svgIcon = this.svgElementFromString(svgData);
25+
this.element.nativeElement.appendChild(this.svgIcon);
26+
}
27+
28+
constructor(private element: ElementRef, private <%= camelize(name) %>Registry: <%= capitalize(camelize(name)) %>Registry,
29+
@Optional() @Inject(DOCUMENT) private document: any) {
30+
}
31+
32+
private svgElementFromString(svgContent: string): SVGElement {
33+
const div = this.document.createElement('DIV');
34+
div.innerHTML = svgContent;
35+
return div.querySelector('svg') || this.document.createElementNS('http://www.w3.org/2000/svg', 'path');
36+
}
37+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { NgModule } from '@angular/core';
2+
import { CommonModule } from '@angular/common';
3+
4+
import { <%= capitalize(camelize(name)) %>Component } from './<%= dasherize(name) %>.component';
5+
6+
@NgModule({
7+
declarations: [<%= capitalize(camelize(name)) %>Component],
8+
exports: [<%= capitalize(camelize(name)) %>Component],
9+
imports: [CommonModule]
10+
})
11+
export class <%= capitalize(camelize(name)) %>Module {}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Tree } from '@angular-devkit/schematics';
2+
import { SchematicTestRunner } from '@angular-devkit/schematics/testing';
3+
import * as path from 'path';
4+
5+
const collectionPath = path.join(__dirname, '../collection.json');
6+
7+
describe('ng-icon-lib', () => {
8+
it('works', async () => {
9+
const runner = new SchematicTestRunner('schematics', collectionPath);
10+
const tree = await runner.runSchematicAsync('ng-icon-lib', {}, Tree.empty()).toPromise();
11+
12+
expect(tree.files).toEqual([]);
13+
});
14+
});

0 commit comments

Comments
 (0)