",
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/request/request.git"
15 | },
16 | "bugs": {
17 | "url": "http://github.com/request/request/issues"
18 | },
19 | "license": "Apache-2.0",
20 | "engines": {
21 | "node": ">= 4"
22 | },
23 | "main": "index.js",
24 | "files": [
25 | "lib/",
26 | "index.js",
27 | "request.js"
28 | ],
29 | "dependencies": {
30 | "aws-sign2": "~0.7.0",
31 | "aws4": "^1.6.0",
32 | "caseless": "~0.12.0",
33 | "combined-stream": "~1.0.5",
34 | "extend": "~3.0.1",
35 | "forever-agent": "~0.6.1",
36 | "form-data": "~2.3.1",
37 | "har-validator": "~5.0.3",
38 | "hawk": "~6.0.2",
39 | "http-signature": "~1.2.0",
40 | "is-typedarray": "~1.0.0",
41 | "isstream": "~0.1.2",
42 | "json-stringify-safe": "~5.0.1",
43 | "mime-types": "~2.1.17",
44 | "oauth-sign": "~0.8.2",
45 | "performance-now": "^2.1.0",
46 | "qs": "~6.5.1",
47 | "safe-buffer": "^5.1.1",
48 | "stringstream": "~0.0.5",
49 | "tough-cookie": "~2.3.3",
50 | "tunnel-agent": "^0.6.0",
51 | "uuid": "^3.1.0"
52 | },
53 | "scripts": {
54 | "test": "npm run lint && npm run test-ci && npm run test-browser",
55 | "test-ci": "taper tests/test-*.js",
56 | "test-cov": "istanbul cover tape tests/test-*.js",
57 | "test-browser": "node tests/browser/start.js",
58 | "lint": "standard"
59 | },
60 | "devDependencies": {
61 | "bluebird": "^3.2.1",
62 | "browserify": "^13.0.1",
63 | "browserify-istanbul": "^2.0.0",
64 | "buffer-equal": "^1.0.0",
65 | "codecov": "^2.0.2",
66 | "coveralls": "^2.11.4",
67 | "function-bind": "^1.0.2",
68 | "istanbul": "^0.4.0",
69 | "karma": "^1.1.1",
70 | "karma-browserify": "^5.0.1",
71 | "karma-cli": "^1.0.0",
72 | "karma-coverage": "^1.0.0",
73 | "karma-phantomjs-launcher": "^1.0.0",
74 | "karma-tap": "^3.0.1",
75 | "phantomjs-prebuilt": "^2.1.3",
76 | "rimraf": "^2.2.8",
77 | "server-destroy": "^1.0.1",
78 | "standard": "^9.0.0",
79 | "tape": "^4.6.0",
80 | "taper": "^0.5.0"
81 | },
82 | "greenkeeper": {
83 | "ignore": [
84 | "hawk",
85 | "har-validator"
86 | ]
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/test/data/@angular/cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@angular/cli",
3 | "version": "1.7.0",
4 | "description": "CLI tool for Angular",
5 | "main": "lib/cli/index.js",
6 | "trackingCode": "UA-8594346-19",
7 | "bin": {
8 | "ng": "./bin/ng"
9 | },
10 | "keywords": [
11 | "angular",
12 | "angular-cli",
13 | "Angular CLI"
14 | ],
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/angular/angular-cli.git"
18 | },
19 | "engines": {
20 | "node": ">= 6.9.0",
21 | "npm": ">= 3.0.0"
22 | },
23 | "author": "Angular Authors",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/angular/angular-cli/issues"
27 | },
28 | "homepage": "https://github.com/angular/angular-cli",
29 | "dependencies": {
30 | "@angular-devkit/build-optimizer": "0.3.1",
31 | "@angular-devkit/core": "0.3.1",
32 | "@angular-devkit/schematics": "0.3.1",
33 | "@ngtools/json-schema": "1.2.0",
34 | "@ngtools/webpack": "1.10.0",
35 | "@schematics/angular": "0.3.1",
36 | "@schematics/package-update": "0.3.1",
37 | "autoprefixer": "^7.2.3",
38 | "cache-loader": "^1.2.0",
39 | "chalk": "~2.2.0",
40 | "circular-dependency-plugin": "^4.2.1",
41 | "clean-css": "^4.1.9",
42 | "common-tags": "^1.3.1",
43 | "copy-webpack-plugin": "~4.4.1",
44 | "core-object": "^3.1.0",
45 | "denodeify": "^1.2.1",
46 | "ember-cli-string-utils": "^1.0.0",
47 | "extract-text-webpack-plugin": "^3.0.2",
48 | "file-loader": "^1.1.5",
49 | "fs-extra": "^4.0.0",
50 | "glob": "^7.0.3",
51 | "html-webpack-plugin": "^2.29.0",
52 | "karma-source-map-support": "^1.2.0",
53 | "less": "^2.7.2",
54 | "less-loader": "^4.0.5",
55 | "license-webpack-plugin": "^1.0.0",
56 | "lodash": "^4.11.1",
57 | "loader-utils": "1.1.0",
58 | "memory-fs": "^0.4.1",
59 | "minimatch": "^3.0.4",
60 | "node-modules-path": "^1.0.0",
61 | "nopt": "^4.0.1",
62 | "opn": "~5.1.0",
63 | "portfinder": "~1.0.12",
64 | "postcss": "^6.0.16",
65 | "postcss-import": "^11.0.0",
66 | "postcss-loader": "^2.0.10",
67 | "postcss-url": "^7.1.2",
68 | "raw-loader": "^0.5.1",
69 | "resolve": "^1.1.7",
70 | "rxjs": "^5.5.6",
71 | "sass-loader": "^6.0.6",
72 | "semver": "^5.1.0",
73 | "silent-error": "^1.0.0",
74 | "source-map-support": "^0.4.1",
75 | "istanbul-instrumenter-loader": "^3.0.0",
76 | "style-loader": "^0.19.1",
77 | "stylus": "^0.54.5",
78 | "stylus-loader": "^3.0.1",
79 | "uglifyjs-webpack-plugin": "^1.1.8",
80 | "url-loader": "^0.6.2",
81 | "webpack": "~3.11.0",
82 | "webpack-dev-middleware": "~1.12.0",
83 | "webpack-dev-server": "~2.11.0",
84 | "webpack-merge": "^4.1.0",
85 | "webpack-sources": "^1.0.0",
86 | "webpack-subresource-integrity": "^1.0.1"
87 | },
88 | "optionalDependencies": {
89 | "node-sass": "^4.7.2"
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/test/data/@nutmeg/cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nutmeg/cli",
3 | "version": "0.11.2",
4 | "description": "Build, test, and publish vanilla Web Components with a little spice",
5 | "main": "dist/cli.js",
6 | "types": "dist/cli.d.ts",
7 | "bin": {
8 | "nutmeg-build": "bin/nutmeg-build",
9 | "nutmeg-clean": "bin/nutmeg-clean",
10 | "nutmeg-new": "bin/nutmeg-new",
11 | "nutmeg-serve": "bin/nutmeg-serve",
12 | "nutmeg-test": "bin/nutmeg-test",
13 | "nutmeg-watch": "bin/nutmeg-watch",
14 | "nutmeg": "bin/nutmeg"
15 | },
16 | "directories": {
17 | "test": "test"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/abraham/nutmeg-cli.git"
22 | },
23 | "keywords": [
24 | "web-components",
25 | "webcomponents",
26 | "shadow-dom",
27 | "shadowdom",
28 | "lit-html",
29 | "nutmeg",
30 | "typescript"
31 | ],
32 | "author": {
33 | "name": "Abraham Williams",
34 | "email": "abraham@abrah.am",
35 | "url": "https://abrah.am"
36 | },
37 | "license": "MIT",
38 | "bugs": {
39 | "url": "https://github.com/abraham/nutmeg-cli/issues"
40 | },
41 | "homepage": "https://github.com/abraham/nutmeg-cli",
42 | "scripts": {
43 | "build": "tsc",
44 | "prepare": "npm run build",
45 | "pretest": "mkdir tmp && npm pack . && node ./scripts/rename-pack.js",
46 | "test": "cd tmp && nutmeg new ci-test first:number second:string third:boolean --no-yarn --cli-source file:../../nutmeg-cli-latest.tgz && cd ci-test && npm run test",
47 | "test:yarn": "cd tmp && nutmeg new ci-test first:number second:string third:boolean --yarn --cli-source file:../.. && cd ci-test && npm run test",
48 | "watch": "tsc --watch"
49 | },
50 | "engines": {
51 | "node": ">=8"
52 | },
53 | "dependencies": {
54 | "@nutmeg/seed": "0.8.2",
55 | "@types/chai": "4.1.2",
56 | "@types/mocha": "2.2.48",
57 | "@types/sinon": "4.1.3",
58 | "@webcomponents/webcomponentsjs": "1.1.0",
59 | "babel-preset-env": "1.6.1",
60 | "chai": "4.1.2",
61 | "commander": "2.13.0",
62 | "hasbin": "1.2.3",
63 | "html-webpack-plugin": "2.30.1",
64 | "karma": "2.0.0",
65 | "karma-chai": "0.1.0",
66 | "karma-chrome-launcher": "2.2.0",
67 | "karma-firefox-launcher": "1.1.0",
68 | "karma-mocha": "1.3.0",
69 | "karma-opera-launcher": "1.0.0",
70 | "karma-safari-launcher": "1.0.0",
71 | "karma-sinon": "1.0.5",
72 | "karma-typescript": "3.0.12",
73 | "karma-typescript-es6-transform": "1.0.3",
74 | "karma-webpack": "2.0.9",
75 | "lodash.template": "4.4.0",
76 | "mocha": "5.0.0",
77 | "pascal-case": "2.0.1",
78 | "recursive-copy": "2.0.8",
79 | "shelljs": "0.8.1",
80 | "sinon": "4.2.2",
81 | "through2": "2.0.3",
82 | "ts-loader": "3.4.0",
83 | "typescript": "2.6.2",
84 | "uglifyjs-webpack-plugin": "1.1.8",
85 | "update-notifier": "2.3.0",
86 | "webpack": "3.10.0",
87 | "webpack-bundle-analyzer": "2.10.0",
88 | "webpack-dev-server": "2.11.1"
89 | },
90 | "devDependencies": {
91 | "@types/lodash.template": "4.4.3",
92 | "@types/node": "9.4.0",
93 | "@types/shelljs": "0.7.8",
94 | "@types/through2": "2.0.33",
95 | "@types/update-notifier": "2.0.0"
96 | },
97 | "browser": {
98 | "fs": false,
99 | "child_process": false
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | <node-package>
2 | ====
3 | [](https://npmjs.com/package/node-package)
4 | [](https://circleci.com/gh/abraham/node-package)
5 | [](https://travis-ci.org/abraham/node-package)
6 | [](https://ci.appveyor.com/project/abraham/node-package)
7 | [](https://david-dm.org/abraham/node-package)
8 | [](https://bundlephobia.com/result?p=node-package)
9 |
10 | Install
11 | ----
12 |
13 | Polyfill tags if you need them. This will include ShadowDOM and Custom Elements support.
14 |
15 | ```
16 |
17 | ```
18 |
19 | Loading this component. It would be a good idea to use a specific version instead of `latest`.
20 |
21 | ```
22 |
23 | ```
24 |
25 | Example
26 | ----
27 |
28 | [Live demo](https://codepen.io/abraham/pen/eVVJrM)
29 |
30 | Usage
31 | ----
32 |
33 | Set the `name` attribute to the name of an [NPM](https://www.npmjs.com/) package.
34 |
35 | ```
36 |
37 | ```
38 |
39 | 
40 |
41 | Add the `global` attribute to add `--global` to the NPM install command.
42 |
43 | ```
44 |
45 | ```
46 |
47 | Will result in `npm install @angular/cli --global`.
48 |
49 | Theming
50 | ----
51 |
52 | For advanced theming you can set the following CSS custom properties:
53 |
54 | - `--node-package-background-color`
55 | - `--node-package-color`
56 | - `--node-package-link-color`
57 |
58 | Blue theme
59 |
60 | ```
61 |
68 |
69 | ```
70 |
71 | 
72 |
73 | Red theme
74 |
75 | ```
76 |
83 |
84 | ```
85 |
86 | 
87 |
88 | Card border
89 |
90 | You can also apply custom edge designs to look more like a card.
91 |
92 | ```
93 |
100 |
101 | ```
102 |
103 | 
104 |
105 | Demo of install commands being copied.
106 |
107 | 
108 |
109 | License
110 | ----
111 |
112 | NodePackage is released under an MIT license.
113 |
114 | Built, tested, and published with [Nutmeg](https://nutmeg.tools).
115 |
--------------------------------------------------------------------------------
/src/success.view.ts:
--------------------------------------------------------------------------------
1 | import { html, svg, TemplateResult } from '@nutmeg/seed';
2 | import { repeat } from 'lit-html/directives/repeat';
3 | import { NodePackage } from './node-package';
4 | import { InstallCommand, InstallSource, Pkg } from './pkg';
5 |
6 | export class SuccessView {
7 | constructor(private component: NodePackage, private pkg: Pkg, private selectedInstallCommand: InstallSource) {}
8 |
9 | public get name(): string {
10 | return this.pkg.name;
11 | }
12 |
13 | public get content(): TemplateResult {
14 | return html`
15 |
16 | ${this.pkg.description}
17 |
18 | ${this.keywords}
19 | ${this.install}
20 | ${this.footer}
21 |
22 | Copied to clipboard
23 |
24 | `;
25 | }
26 |
27 | private selectInstallCommand(event: MouseEvent, command: InstallCommand): void {
28 | event.preventDefault();
29 | this.component.selectedTab = command.id;
30 | this.component.render();
31 | }
32 |
33 | private copyInstallCommand(event: MouseEvent): void {
34 | event.preventDefault();
35 | (this.component.$('.command:not(.hidden)') as HTMLInputElement).select();
36 | document.execCommand('copy');
37 | this.component.$('#toast').classList.add('copied');
38 | setTimeout(() => {
39 | this.component.$('#toast').classList.remove('copied');
40 | }, 2750);
41 | }
42 |
43 | private keyword(keyword: string): TemplateResult {
44 | return html`
45 | #${keyword}
46 | `;
47 | }
48 |
49 | private get keywords(): TemplateResult {
50 | return html`
51 |
52 | ${repeat(this.pkg.keywords, keyword => keyword, (keyword, _index) => this.keyword(keyword))}
53 |
54 | `;
55 | }
56 |
57 | private get types(): TemplateResult {
58 | return html`
59 | Includes types
60 | `;
61 | }
62 |
63 | private installTab(command: InstallCommand): TemplateResult {
64 | const classes = `item tab ${this.selectedInstallCommand === command.id ? 'selected' : ''}`;
65 | return html`
66 | this.selectInstallCommand(event, command)}>
67 |
70 |
71 | `;
72 | }
73 |
74 | private installCommand(command: InstallCommand): TemplateResult {
75 | return html`
76 |
77 | `;
78 | }
79 |
80 | private get install(): TemplateResult {
81 | return html`
82 |
83 |
84 | ${repeat(this.pkg.installCommands(this.component.global), command => command.id, (command, _index) => this.installTab(command))}
85 |
86 |
87 | ${repeat(this.pkg.installCommands(this.component.global), command => command.id, (command, _index) => this.installCommand(command))}
88 |
91 |
92 |
93 | `;
94 | }
95 |
96 | private get footer(): TemplateResult {
97 | return html`
98 |
107 | `;
108 | }
109 |
110 | private get copy(): TemplateResult {
111 | return svg`
112 |
116 | `;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/pkg.ts:
--------------------------------------------------------------------------------
1 | export type InstallSource = 'npm' | 'git' | 'unpkg';
2 |
3 | export const DEFAULT_INSTALL_SOURCE: InstallSource = 'npm';
4 |
5 | export interface InstallCommand {
6 | command: string;
7 | id: InstallSource;
8 | }
9 |
10 | export class Pkg {
11 | constructor(private data: PackageData) {
12 | }
13 |
14 | public get description(): string {
15 | return this.data.description || '';
16 | }
17 |
18 | public get git(): string {
19 | if (typeof this.data.repository === 'object' && this.data.repository.type === 'git') {
20 | return this.data.repository.url;
21 | } else {
22 | return '';
23 | }
24 | }
25 |
26 | public get keywords(): string[] {
27 | return this.dirtyKeywords.map(keyword => keyword.trim());
28 | }
29 |
30 | public get license(): string {
31 | if (typeof this.data.license === 'string') {
32 | return this.data.license;
33 | } else {
34 | return '';
35 | }
36 | }
37 |
38 | public installCommands(global: boolean): InstallCommand[] {
39 | const commands: InstallCommand[] = [
40 | {
41 | command: `npm install ${this.name}${global ? ' --global' : ''}`,
42 | id: 'npm',
43 | }
44 | ];
45 | if (this.git) {
46 | commands.push({
47 | command: `git clone ${this.git}`,
48 | id: 'git',
49 | });
50 | }
51 | if (this.unpkg) {
52 | commands.push({
53 | command: ``,
54 | id: 'unpkg',
55 | });
56 | }
57 | return commands;
58 | }
59 |
60 | public get name(): string {
61 | return this.data.name;
62 | }
63 |
64 | public get types(): string {
65 | return this.data.types || this.data.typings || '';
66 | }
67 |
68 | public get version(): string {
69 | return this.data.version;
70 | }
71 |
72 | public get unpkg(): string {
73 | return this.webpath ? new URL(`${this.name}/${this.webpath}`, 'https://unpkg.com/').href : '';
74 | }
75 |
76 | private get dirtyKeywords(): string[] {
77 | if (!this.data.keywords) {
78 | return [];
79 | }
80 | if (typeof this.data.keywords === 'string') {
81 | return this.data.keywords.split(',');
82 | }
83 | return this.data.keywords;
84 | }
85 |
86 | private get webpath(): string {
87 | if (!!this.data.unpkg) {
88 | return this.data.unpkg;
89 | } else if (!!this.data.browser) {
90 | return this.data.browser;
91 | } else if (!!this.data.main) {
92 | return this.data.main;
93 | } else if (!!this.data.webpack) {
94 | return this.data.webpack;
95 | } else {
96 | return '';
97 | }
98 | }
99 | }
100 |
101 | export interface PackageData {
102 | author?: string | PersonData;
103 | bin?: string | {[index: string]: string};
104 | bugs?: string | BugsData;
105 | bundledDependencies?: DependenciesData;
106 | config?: ConfigData;
107 | contributors?: Array;
108 | cpu?: string[];
109 | dependencies?: DependenciesData;
110 | description?: string;
111 | devDependencies?: DependenciesData;
112 | directories?: DirectoriesData;
113 | engines?: EnginesData;
114 | files?: string[];
115 | homepage?: string;
116 | keywords?: string[] | string;
117 | license?: string | DeprecatedLicenseData;
118 | main?: string;
119 | man?: string | string[];
120 | name: string;
121 | optionalDependencies?: DependenciesData;
122 | os?: string[];
123 | peerDependencies?: DependenciesData;
124 | private?: boolean;
125 | publishConfig?: ConfigData;
126 | repository?: string | RepositoryData;
127 | scripts?: {[index: string]: string};
128 | version: string;
129 |
130 | // Non-standard
131 | browser?: string;
132 | readmeFilename?: string;
133 | types?: string;
134 | typings?: string;
135 | unpkg?: string;
136 | webpack?: string;
137 | }
138 |
139 | export interface BugsData {
140 | url?: string;
141 | email?: string;
142 | }
143 |
144 | export interface DeprecatedLicenseData {
145 | type: string;
146 | url: string;
147 | }
148 |
149 | export interface PersonData {
150 | name: string;
151 | email?: string;
152 | url?: string;
153 | }
154 |
155 | export interface DirectoriesData {
156 | lib?: string;
157 | bin?: string;
158 | man?: string;
159 | doc?: string;
160 | example?: string;
161 | test?: string;
162 | }
163 |
164 | export interface RepositoryData {
165 | type: string;
166 | url: string;
167 | }
168 |
169 | export interface DependenciesData {
170 | [index: string]: string
171 | }
172 |
173 | export interface EnginesData {
174 | node: string;
175 | npm?: string;
176 | }
177 |
178 | export interface ConfigData {
179 | [index: string]: string;
180 | }
181 |
--------------------------------------------------------------------------------
/src/node-package.ts:
--------------------------------------------------------------------------------
1 | import { Failure, fold, Initialized, Pending, RemoteData, Success } from '@abraham/remotedata';
2 | import { html, property, Seed, svg, TemplateResult } from '@nutmeg/seed';
3 | import { Api } from './api';
4 | import { FailureView } from './failure.view';
5 | import { PendingView } from './pending.view';
6 | import { DEFAULT_INSTALL_SOURCE, InstallSource, Pkg } from './pkg';
7 | import { SuccessView } from './success.view';
8 |
9 | interface SuccessData {
10 | pkg: Pkg,
11 | selectedTab: InstallSource,
12 | }
13 |
14 | type State = RemoteData;
15 |
16 | export class NodePackage extends Seed {
17 | @property({ type: Boolean }) public global: boolean = false;
18 | @property({ type: String }) public name?: string;
19 |
20 | private api = new Api();
21 | private state: State = new Initialized();
22 |
23 | constructor() {
24 | super();
25 | }
26 |
27 | /** The component instance has been inserted into the DOM. */
28 | public connectedCallback() {
29 | super.connectedCallback();
30 | }
31 |
32 | /** The component instance has been removed from the DOM. */
33 | public disconnectedCallback() {
34 | super.disconnectedCallback();
35 | }
36 |
37 | /** Watch for changes to these attributes. */
38 | public static get observedAttributes(): string[] {
39 | return super.observedAttributes;
40 | }
41 |
42 | /** Rerender when the observed attributes change. */
43 | public attributeChangedCallback(name: string, oldValue: any, newValue: any) {
44 | super.attributeChangedCallback(name, oldValue, newValue);
45 | }
46 |
47 | /** Styling for the component. */
48 | public get styles(): TemplateResult {
49 | return html`
50 |
218 | `;
219 | }
220 |
221 | public get logo(): TemplateResult {
222 | return svg`
223 |
229 | `;
230 | }
231 |
232 | private get header(): TemplateResult {
233 | if (this.name) {
234 | return html`
235 |
243 | `;
244 | } else {
245 | return html`
246 |
252 | `;
253 | }
254 | }
255 |
256 | /** HTML Template for the component. */
257 | public get template(): TemplateResult {
258 | return html`
259 |
260 | ${this.header}
261 | ${this.view(this.state)}
262 |
263 | `;
264 | }
265 |
266 | public set selectedTab(tab: InstallSource) {
267 | if (this.state instanceof Success) {
268 | this.state.data.selectedTab = tab;
269 | }
270 | }
271 |
272 | private pendingHandler(): TemplateResult {
273 | return new PendingView().content;
274 | }
275 |
276 | private initializedHandler(): TemplateResult {
277 | if (this.name) {
278 | this.fetchPackage();
279 | return this.pendingHandler()
280 | } else {
281 | return new FailureView('Missing required value "name"').content;
282 | }
283 | }
284 |
285 | private errorHandler(error: string): TemplateResult {
286 | if (this.updateData) { this.fetchPackage(); }
287 | return new FailureView(error).content;
288 | }
289 |
290 | private successHandler(data: SuccessData): TemplateResult {
291 | if (this.updateData) { this.fetchPackage(); }
292 | return new SuccessView(this, data.pkg, data.selectedTab).content;
293 | }
294 |
295 | private get view(): (state: State) => TemplateResult {
296 | return fold(
297 | () => this.initializedHandler(),
298 | () => this.pendingHandler(),
299 | (error: string) => this.errorHandler(error),
300 | (data: SuccessData) => this.successHandler(data),
301 | );
302 | }
303 |
304 | private async fetchPackage(): Promise {
305 | if (this.name) {
306 | this.state = new Pending();
307 | try {
308 | const pkg = new Pkg(await this.api.fetch(this.name));
309 | this.state = new Success({ selectedTab: DEFAULT_INSTALL_SOURCE, pkg });
310 | } catch (error) {
311 | this.state = new Failure(error);
312 | }
313 | this.render();
314 | }
315 | }
316 |
317 | private get updateData(): boolean {
318 | return this.state instanceof Success && this.state.data.pkg.name !== this.name;
319 | }
320 | }
321 |
322 | window.customElements.define('node-package', NodePackage);
323 |
--------------------------------------------------------------------------------
/test/node-package.test.ts:
--------------------------------------------------------------------------------
1 | import 'mocha';
2 | import { expect } from 'chai';
3 | import * as sinon from 'sinon';
4 |
5 | import { NodePackage } from '../src/node-package';
6 |
7 | // Increase timeout for AppVeyor
8 | const TIMEOUT = 500;
9 |
10 | describe('', () => {
11 | let component: NodePackage;
12 | let stub: sinon.SinonStub;
13 |
14 | before(() => {
15 | const realFetch = window.fetch;
16 | stub = sinon.stub(window, 'fetch').callsFake((url: string) => {
17 | const localUrl = url.replace('https://unpkg.com/', './base/test/data/').replace('/package.json', '.json')
18 | return realFetch(localUrl);
19 | });
20 | });
21 |
22 | after(() => {
23 | stub.restore();
24 | });
25 |
26 | describe('valid package', () => {
27 | beforeEach(async () => {
28 | component = fixture('');
29 | await sleep(TIMEOUT);
30 | });
31 |
32 | describe('header', () => {
33 | it('renders the package name', () => {
34 | const links = component.$$('#header a') as NodeListOf;
35 | const name = links[0];
36 | expect(name.innerText).to.eq('bluebird');
37 | expect(name.href).to.eq('https://npmjs.com/package/bluebird');
38 | });
39 |
40 | it('renders the NPM logo', () => {
41 | const links = component.$$('#header a') as NodeListOf;
42 | const name = links[1];
43 | expect(name.href).to.eq('https://npmjs.com/package/bluebird');
44 | expect(name.querySelector('svg#logo')).to.exist;
45 | });
46 | });
47 |
48 | describe('description', () => {
49 | it('renders the package description', () => {
50 | const description = component.$('#description') as HTMLDivElement;
51 | expect(description.innerText).to
52 | .eq('Full featured Promises/A+ implementation with exceptionally good performance');
53 | });
54 | });
55 |
56 | describe('keywords', () => {
57 | it('link to NPM search', () => {
58 | const keywords = component.$$('#keywords a') as NodeListOf;
59 | expect(keywords.length).to.eq(13);
60 | expect(keywords[0].href).to.eq('https://www.npmjs.com/browse/keyword/promise');
61 | expect(keywords[0].innerText).to.eq('#promise');
62 | });
63 |
64 | describe('when a string', () => {
65 | beforeEach(async () => {
66 | component = fixture('');
67 | await sleep(TIMEOUT);
68 | });
69 |
70 | it('link to NPM search', () => {
71 | const keywords = component.$$('#keywords a') as NodeListOf;
72 | expect(keywords.length).to.eq(3);
73 | expect(keywords[0].href).to.eq('https://www.npmjs.com/browse/keyword/modules');
74 | expect(keywords[0].innerText).to.eq('#modules');
75 | });
76 | });
77 | });
78 |
79 | describe('install', () => {
80 | it('renders the tabs', () => {
81 | const items = component.$$('#tabs .tab') as NodeListOf;
82 | expect(items.length).to.eq(3);
83 | expect(items[0].innerText).to.eq('NPM');
84 | expect(items[1].innerText).to.eq('GIT');
85 | expect(items[2].innerText).to.eq('UNPKG');
86 | });
87 |
88 | it('shows one command', () => {
89 | expect(component.$$('#install .command').length).to.eq(3);
90 | expect(component.$$('#install .command.hidden').length).to.eq(2);
91 | });
92 |
93 | it('shows commands as readonly', () => {
94 | expect(component.$$('#install .command[readonly]').length).to.eq(3);
95 | });
96 |
97 | function assertCommand(index: number, command: string) {
98 | let commands = component.$$('#install .command') as NodeListOf;
99 | let tabs = component.$$('#tabs .tab') as NodeListOf;
100 | for (var i = 0; i < tabs.length; i++) {
101 | if (i === index) {
102 | expect(commands[i].value).to.eq(command);
103 | expect(commands[i].classList.contains('hidden')).to.be.false;
104 | expect(tabs[i].classList.contains('selected')).to.be.true;
105 | } else {
106 | expect(commands[i].classList.contains('hidden')).to.be.true;
107 | expect(tabs[i].classList.contains('selected')).to.be.false;
108 | }
109 | }
110 | }
111 |
112 | it('changes command to match tab', async () => {
113 | let tabs = component.$$('#tabs .tab') as NodeListOf;
114 | assertCommand(0, 'npm install bluebird');
115 | tabs[1].click();
116 | await sleep(TIMEOUT);
117 | assertCommand(1, 'git clone git://github.com/petkaantonov/bluebird.git');
118 | tabs[2].click();
119 | await sleep(TIMEOUT);
120 | assertCommand(2, '');
121 | });
122 |
123 | describe('without web or git', () => {
124 | beforeEach(async () => {
125 | component = fixture('');
126 | await sleep(TIMEOUT);
127 | });
128 |
129 | it('renders the tabs', () => {
130 | const items = component.$$('#tabs .tab') as NodeListOf;
131 | expect(items.length).to.eq(1);
132 | expect(items[0].innerText).to.eq('NPM');
133 | });
134 | });
135 |
136 | describe('copy command', () => {
137 | it('copies command', async () => {
138 | const copy = sinon.spy(document, 'execCommand');
139 | const input = component.$('#npm') as HTMLInputElement;
140 | component.$('#copy').click();
141 | expect(copy.called).to.be.true;
142 | expect(input.selectionStart).to.eq(0);
143 | expect(input.selectionEnd).to.eq(20);
144 | expect(component.$('#toast').classList.contains('copied')).to.be.true;
145 | await sleep(2750);
146 | expect(component.$('#toast').classList.contains('copied')).to.be.false;
147 | }).timeout(5000);
148 | });
149 |
150 | describe('with global', () => {
151 | beforeEach(async () => {
152 | component = fixture('');
153 | await sleep(TIMEOUT);
154 | });
155 |
156 | it('renders --global', () => {
157 | assertCommand(0, 'npm install @angular/cli --global');
158 | });
159 | });
160 | });
161 |
162 | describe('footer', () => {
163 | it('renders the details', () => {
164 | const items = component.$$('#footer .item') as NodeListOf;
165 | expect(items.length).to.eq(2);
166 | expect(items[0].innerText).to.eq('v3.5.1');
167 | expect(items[1].innerText).to.eq('MIT');
168 | });
169 |
170 | describe('with types', () => {
171 | beforeEach(async () => {
172 | component = fixture('');
173 | await sleep(TIMEOUT);
174 | });
175 |
176 | it('renders types', () => {
177 | const items = component.$$('#footer .item') as NodeListOf;
178 | expect(items.length).to.eq(3);
179 | expect(items[1].innerText).to.eq('Includes types');
180 | });
181 | });
182 | });
183 |
184 | describe('theme', () => {
185 | describe('with default colors', () => {
186 | beforeEach(async () => {
187 | component = fixture('');
188 | await sleep(TIMEOUT);
189 | });
190 |
191 | it('is pretty', () => {
192 | expect(getComputedStyle(component.$('#content')).backgroundColor).equal('rgb(255, 255, 255)');
193 | expect(getComputedStyle(component.$('#header')).color).equal('rgb(32, 33, 36)');
194 | expect(getComputedStyle(component.$('#description')).color).equal('rgb(32, 33, 36)');
195 | expect(getComputedStyle(component.$('#keywords .keyword')).color).equal('rgb(203, 56, 55)');
196 | expect(getComputedStyle(component.$('#tabs .tab')).backgroundColor).equal('rgb(255, 255, 255)');
197 | expect(getComputedStyle(component.$('#tabs .tab')).color).equal('rgb(203, 56, 55)');
198 | expect(getComputedStyle(component.$('#install .command')).backgroundColor).equal('rgb(218, 220, 224)');
199 | expect(getComputedStyle(component.$('#install .command')).color).equal('rgb(32, 33, 36)');
200 | expect(getComputedStyle(component.$('#footer')).color).equal('rgb(32, 33, 36)');
201 | });
202 | });
203 |
204 | describe('with custom colors', () => {
205 | beforeEach(async () => {
206 | component = fixture(`
207 |
208 |
215 |
216 |
217 | `).querySelector('node-package') as NodePackage;
218 | await sleep(TIMEOUT);
219 | });
220 |
221 | it('is pretty', () => {
222 | expect(getComputedStyle(component.$('#content')).backgroundColor).equal('rgb(3, 169, 244)');
223 | expect(getComputedStyle(component.$('#header')).color).equal('rgb(255, 255, 255)');
224 | expect(getComputedStyle(component.$('#description')).color).equal('rgb(255, 255, 255)');
225 | expect(getComputedStyle(component.$('#keywords .keyword')).color).equal('rgb(218, 220, 224)');
226 | expect(getComputedStyle(component.$('#tabs .tab')).backgroundColor).equal('rgb(3, 169, 244)');
227 | expect(getComputedStyle(component.$('#tabs .tab')).color).equal('rgb(218, 220, 224)');
228 | expect(getComputedStyle(component.$('#install .command')).backgroundColor).equal('rgb(255, 255, 255)');
229 | expect(getComputedStyle(component.$('#install .command')).color).equal('rgb(3, 169, 244)');
230 | expect(getComputedStyle(component.$('#footer')).color).equal('rgb(255, 255, 255)');
231 | });
232 | });
233 | });
234 | });
235 |
236 | describe('error', () => {
237 | beforeEach(async () => {
238 | stub.callsFake(() => {
239 | return new Response('Cannot find package 404@0.0.0', { status: 404 });
240 | });
241 | component = fixture('');
242 | await sleep(TIMEOUT);
243 | });
244 |
245 | afterEach(() => {
246 | stub.restore();
247 | });
248 |
249 | it('renders error message', () => {
250 | expect(component.$('#error').innerText).to.eq('Cannot find package 404@0.0.0');
251 | });
252 | });
253 |
254 | describe('missing value', () => {
255 | beforeEach(async () => {
256 | component = fixture('');
257 | });
258 |
259 | it('renders error message', () => {
260 | expect(component.$('#error').innerText).to.eq('Missing required value "name"');
261 | });
262 | });
263 | });
264 |
265 | function fixture(tag: string): NodePackage {
266 | function fixtureContainer(): HTMLElement {
267 | let div = document.createElement('div');
268 | div.classList.add('fixture');
269 | return div;
270 | }
271 | let fixture = document.body.querySelector('.fixture') || document.body.appendChild(fixtureContainer());
272 | fixture.innerHTML = tag;
273 | return fixture.children[0] as NodePackage;
274 | }
275 |
276 | function sleep(ms: number): Promise<{}> {
277 | return new Promise(resolve => setTimeout(resolve, ms));
278 | }
279 |
--------------------------------------------------------------------------------