diff --git a/Makefile b/Makefile index 34cf61e9..f752f616 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ default: install h help: - @egrep '^\S|^$$' Makefile + @egrep '^[a-z#]' Makefile .PHONY: hooks @@ -14,10 +14,12 @@ install: l lint: yarn lint:fix + t test: + yarn compile yarn test:unit -s serve: lint test +s serve: lint yarn start diff --git a/docs/development.md b/docs/development.md index ca51ced6..53a6d6f2 100644 --- a/docs/development.md +++ b/docs/development.md @@ -4,7 +4,7 @@ ## Roadmap -- [ ] Add tests +- [x] Add tests - [ ] Refactor JS to be DRY - [x] Add TS support - [ ] Use interfaces and types @@ -166,10 +166,49 @@ Therefore formatting rules can be set at a few levels, which makes things confus - Editor Config file (for indentation at least.) +## Tests + +All functions tested needed `export` so they can be imported. A leading underscore is used to indicate functions are private - intended for use only within a module and not to be called directly (except in tests). + +A template for new tests: + +``` +describe("#foo", () => { + it("", () => { + expect( + foo( + "" + ) + ).toBe(""); + }); +}); +``` + + ## Packages On upgrading packages. +Check outdated plugins: + +```sh +$ npx vue outdated +``` + +Outdated packages: + +```sh +$ yarn outdated +``` + +Upgrade: + +```sh +$ yarn add foo +``` + +That will use the high available without conflicting with extant packages. + I attempted to upgrade these outdated packages as follows: ``` diff --git a/docs/usage.md b/docs/usage.md index 250f39ef..6d7a6c17 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -3,11 +3,12 @@ ## Run dev server This will compile TS to JS and serve the app. + ```sh $ yarn start ``` -This does the same but adds lint fixes and tests first. +This does the same but adds lint fixes first. Tests are left out so the app can still be used without passing tests. ```sh $ make serve @@ -39,11 +40,17 @@ Run unit tests. ```sh $ yarn test:unit -$ # Or +``` + +Note that it able to work with the TS files directly. It this does need or produce any output JS files. + +To add the ability to get errors from the TypeScript compiler (such as bad use of arguments) before the tests are run, use this: + +```sh $ make test ``` -Note that this does need or produce any output directory. It operates directly on the `.ts` files. +Or just look for TypeScript errors in the IDE. ## Compile diff --git a/jest.config.js b/jest.config.js index f9d5bfe8..58969d35 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,4 @@ module.exports = { preset: "@vue/cli-plugin-unit-jest/presets/typescript-and-babel", + testPathIgnorePatterns: ["/dist/"], }; diff --git a/src/core/Repo.ts b/src/core/Repo.ts index 396c410c..f8560dff 100644 --- a/src/core/Repo.ts +++ b/src/core/Repo.ts @@ -9,6 +9,7 @@ import { GITHUB_GREEN, GITHUB_IO, GREEN, + LICENSE, SHIELDS_GH, // eslint-disable-next-line prettier/prettier STYLES @@ -103,24 +104,27 @@ export class Repo { return markdownImageWithLink(title, imgUrl, target); } + _licenseTarget(localLicense: boolean) { + if (localLicense) { + return "#license"; + } + const repoUrl = this.ghURL(); + + return `${repoUrl}/blob/${DEFAULT_BRANCH}/LICENSE`; + } + licenseBadge(licenseType: string, localLicense = true) { if (!licenseType || !this._isValid()) { return ""; } - const label = "License", - message = licenseType, - color = DEFAULT_COLOR, - isLarge = false; - - let target; - if (localLicense) { - target = "#license"; - } else { - const repoUrl = this.ghURL(); - target = `${repoUrl}/blob/${DEFAULT_BRANCH}/LICENSE`; - } - return genericBadge(label, message, color, isLarge, target); + return genericBadge( + LICENSE.LABEL, + licenseType, + LICENSE.COLOR, + LICENSE.IS_LARGE, + this._licenseTarget(localLicense) + ); } gh() { @@ -146,7 +150,7 @@ export class Repo { } private _ghSocialShield(type: string) { - return `${SHIELDS_GH}/${type}/${this.username}/${this.repoName}${STYLES.SOCIAL}`; + return `${SHIELDS_GH}/${type}/${this.username}/${this.repoName}?style=${STYLES.SOCIAL}`; } /* Stars or forks counter */ diff --git a/src/core/badges.d.ts b/src/core/badges.d.ts new file mode 100644 index 00000000..67e44872 --- /dev/null +++ b/src/core/badges.d.ts @@ -0,0 +1,7 @@ +type StrMap = { [key: string]: string }; + +export type GenericBadge = { + label?: string; + message: string; + color: string; +}; diff --git a/src/core/badges.ts b/src/core/badges.ts index 2712094a..752c8640 100644 --- a/src/core/badges.ts +++ b/src/core/badges.ts @@ -1,7 +1,8 @@ /** * Handle rendering of each badge and all badges. */ -import { DEFAULT_COLOR, SHIELDS_BADGE, SHIELDS_STATIC } from "./constants"; +import { GenericBadge, StrMap } from "./badges.d"; +import { SHIELDS_BADGE, SHIELDS_STATIC, STYLES } from "./constants"; // TODO combine link/target functions in a module. export function markdownLink(altText: string, linkTarget: string) { @@ -20,6 +21,12 @@ export function markdownImage( } // TODO: Add pre-label as social badges have. +/** + * Create a markdown image tag with external link. + * + * This performs no encoding - the inputs should be encoded already to be a URL without spaces and + * to be a valid URL for shields.io API. + */ export function markdownImageWithLink( altText: string, imageTarget: string, @@ -35,58 +42,52 @@ export function markdownImageWithLink( } /** - * Encode a value to safe as a param in a URL. - * - * Prepare a value for dash-baseds shields.io API based on notes on the site. - * The builtin encodeURI function is used to handle spaces and special characters. - * - * Note that '>' and '<' are valid on shields.io and should not be encoded. - * - * e.g. 'Foo Bar_Baz-Buzz' becomes 'Foo_Bar__Baz--Buzz'. - * Note the API itself does funny things if you do use more than one - * occurence of dash and space or underscore and space when when this is escaped correctly. - * e.g. 'A - B - C' converted to 'A_--_B_--_C' renders as 'A - B_- C'. - * So just don't mix them and you'll be ok - like with 'A-B-C'. + * Replace dashes, underscores and spaces to match shields.io API format. */ -function encode(value: string, spaceToUnderscore = true) { +export function _encodeSeparators(value: string, spaceToUnderscore: boolean) { value = value.replace(/-/g, "--").replace(/_/g, "__"); if (spaceToUnderscore) { value = value.replace(/ /g, "_"); } - const encoded = encodeURI(value); - - return encoded.replace(/%3E/g, ">").replace(/%3C/g, "<"); + return value; } /** - * Make a fixed markdown badge using any given inputs. + * Turn URL-encoded '<' and '>' symbols back into readable characters. * - * Escapes URLs. - * TODO: Avoid escaping if internal URLs. - * TODO: Maybe remove this function. + * These are allowed in shields.io URLs so should not be encoded. */ -export function makeBadge( - title: string, - imageTarget: string, - linkTarget: string -) { - return markdownImageWithLink(title, encode(imageTarget), encode(linkTarget)); +export function _decodeAngleBrackets(value: string) { + return value.replace(/%3E/g, ">").replace(/%3C/g, "<"); } /** - * Serialize a URL from query params. + * Encode a value to be safe as a param in a URL. * - * Note the URL must have a protocal or it will be considered invalid. Any empty values get - * dropped to keep the result short. + * Prepare a value for dash-based shields.io API based on notes on that site. * - * The URL types's API performs encoding, so at the end we must reverse this so the result works for badges. + * Note the shields.io API itself does funny things if you do use more than one + * occurence of dash and space or underscore and space when this is escaped correctly. + * e.g. 'A - B - C' converted to 'A_--_B_--_C' unfortunately renders in the SVG result 'A - B_- C'. + * So just don't mix them and you'll be ok. Like do 'A-B-C'. */ -export function buildUrl( - urlStr: string, - params: { [key: string]: string } -): string { +export function _encodeParam(value: string, spaceToUnderscore = true) { + value = _encodeSeparators(value, spaceToUnderscore); + + const encoded = encodeURIComponent(value); + + return _decodeAngleBrackets(encoded); +} + +/** + * Serialize a URL using query params. + * + * The URL must have a protocol or it will be considered invalid. We drop any empty values to keep + * the result short. + */ +export function buildUrl(urlStr: string, params: StrMap): string { let url = new URL(urlStr); for (const [key, value] of Object.entries(params)) { @@ -98,24 +99,25 @@ export function buildUrl( return decodeURI(url.href); } -function formatTitle(label: string, message: string) { +export function _formatTitle(label: string, message: string) { return label ? [label, message].join(" - ") : message; } /** - * Prepare path for shields.io dash-based API. + * Create a URL path for the shields.io dash-based API. * * The API requires MESSAGE-COLOR at the least and also accepts LABEL-MESSAGE-COLOR. * * This appropriately escapes label and message for you, based on notes on the shields.io website. * So you can pass in more readable values. */ -function dashShieldPath(message: string, color: string, label?: string) { - message = encode(message); +export function _dashShieldPath(badge: GenericBadge) { + const message = _encodeParam(badge.message); + let label = badge.label; - let pieces = [message, color]; + let pieces = [message, badge.color]; if (label) { - label = encode(label); + label = _encodeParam(label); pieces.unshift(label); } @@ -123,15 +125,13 @@ function dashShieldPath(message: string, color: string, label?: string) { } /** - * Generate parametes to style a badge. - * - * Return as key-value pairs with appropriate size (large or standard) and optional logo. + * Generate parameters for stying a badge. */ export function logoParams(isLarge = false, logo?: string, logoColor?: string) { - let params: { [key: string]: string } = {}; + let params: StrMap = {}; if (isLarge) { - params.style = "for-the-badge"; + params.style = STYLES.FOR_THE_BADGE; } if (logo) { @@ -145,24 +145,23 @@ export function logoParams(isLarge = false, logo?: string, logoColor?: string) { return params; } -interface GenericBadge { - label: string; - message: string; - color: string; - styleParams: { [key: string]: string }; -} - -// TODO: Move business logic for specific badges to separate module from general markdown and URL handling. +// TODO: Move business logic for specific badges to separate module from general markdown and URL +// handling. /** Image URL for param-based static badge. */ -function staticParamsUrl({ label, message, color, styleParams }: GenericBadge) { - const params = { label, message, color, ...styleParams }; +export function _staticParamsUrl(badge: GenericBadge, styleParams: StrMap) { + const params = { + label: badge.label!, + message: badge.message, + color: badge.color, + ...styleParams, + }; return buildUrl(SHIELDS_STATIC, params); } -/** Image URL for dash-based static badge. */ -function staticDashUrl({ label, message, color, styleParams }: GenericBadge) { - const imgPath = dashShieldPath(message, color, label), +/** Image URL for a dash-based static badge. */ +export function _staticDashUrl(badge: GenericBadge, styleParams: StrMap) { + const imgPath = _dashShieldPath(badge), imgUrl = `${SHIELDS_BADGE}/${imgPath}`; return buildUrl(imgUrl, styleParams); @@ -175,35 +174,31 @@ function staticDashUrl({ label, message, color, styleParams }: GenericBadge) { * Everything is optional except message. * * In the dash style, the result is LABEL-MESSAGE-COLOR or MESSABE-COLOR. The API needs color to be - * set. - * Sample: https://img.shields.io/badge/Foo-Bar--Baz-green + * set, so this is made a required param here on this function. + * Sample URL: https://img.shields.io/badge/Foo-Bar--Baz-green * - * Use the params style by setting onlyQueryParams to be true. There result is more verbose but does - * not required escaping characters. Sample: + * Use the params style by setting onlyQueryParams to be true. The result is more verbose but does + * not require escaping characters. Sample: * https://img.shields.io/static/v1?label=MichaelCurrin&message=badge-generator&logo=github&color=blue */ export function genericBadge( label = "", message: string, - color = "", + color: string, isLarge = false, target = "", logo = "", logoColor = "", onlyQueryParams = false ) { - if (!color) { - color = DEFAULT_COLOR; - } - - const title = formatTitle(label, message); + const title = _formatTitle(label, message); - const styleParams = logoParams(isLarge, logo, logoColor), - badgeFields = { label, message, color, styleParams }; + const badgeFields = { label, message, color }, + styleParams = logoParams(isLarge, logo, logoColor); const fullImgUrl = onlyQueryParams - ? staticParamsUrl(badgeFields) - : staticDashUrl(badgeFields); + ? _staticParamsUrl(badgeFields, styleParams) + : _staticDashUrl(badgeFields, styleParams); return markdownImageWithLink(title, fullImgUrl, target); } diff --git a/src/core/constants.ts b/src/core/constants.ts index 9e0d5f59..530e9846 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -11,17 +11,22 @@ export const GITHUB_DOMAIN = "https://github.com", GITHUB_IO = "github.io", DEFAULT_COLOR = "blue", GREEN = "green", - // From 'Use this template' or 'Merge' button. + // From 'Use this template' or 'Merge' button on GitHub. GITHUB_GREEN = "2ea44f", DEFAULT_BRANCH = "master"; +export const LICENSE = { + LABEL: "License", + COLOR: DEFAULT_COLOR, + IS_LARGE: false, +}; + // TODO handle as map to make it easy to add multiple params. export const STYLES = { - FOR_THE_BADGE: "?style=for-the-badge", - SOCIAL: "?style=social", + FOR_THE_BADGE: "for-the-badge", + SOCIAL: "social", }; -// Excluding trailing slash makes URL joins more natural. export enum REGISTRY { Python = "https://pypi.org/project", Node = "https://www.npmjs.com/package", diff --git a/src/core/packages.ts b/src/core/packages.ts index b4dc5a7f..8acf57b6 100644 --- a/src/core/packages.ts +++ b/src/core/packages.ts @@ -10,6 +10,9 @@ import { } from "./badges"; import { DEFAULT_COLOR, REGISTRY, SHIELDS_PACKAGE } from "./constants"; +/** + * Static dependency badge. + */ export function dependency(name: string, registry: REGISTRY) { const isLarge = false; const url = `${registry}/${name}`; @@ -18,14 +21,21 @@ export function dependency(name: string, registry: REGISTRY) { } /** - * Supports NPM packages. + * Dynamic Node package badge. + * + * The badge will dynamically display given package's locked version number, using your repo's + * package.json file. + * + * Note - the badge URL needs something added to work for dev dependencies. I'm happy to not support + * that now. * - * Shields.io supports Pipenv lock files but not requirements.txt file, it seems. And not Gemfile either. + * Shields.io does Pipenv lock files, but not requirements.txt file, it seems. And not Gemfile + * either. */ export function nodeVersionBadge( username: string, repoName: string, - pkgName?: string, + pkgName: string, logo?: string, logoColor?: string ) { diff --git a/tests/unit/core/Repo.spec.ts b/tests/unit/core/Repo.spec.ts new file mode 100644 index 00000000..b15931a4 --- /dev/null +++ b/tests/unit/core/Repo.spec.ts @@ -0,0 +1,21 @@ +import { Repo } from "@/core/Repo"; + +describe("#Repo", () => { + describe("#licenseBadge", () => { + const repo = new Repo("MichaelCurrin", "badge-generator"); + + it("return a badge for a local license", () => { + expect(repo.licenseBadge("MIT", true)).toBe( + "[![License - MIT](https://img.shields.io/badge/License-MIT-blue)](#license)" + ); + }); + + it("return a badge for a remote license", () => { + const target = + "https://github.com/MichaelCurrin/badge-generator/blob/master/LICENSE", + expectedBadge = `[![License - MIT](https://img.shields.io/badge/License-MIT-blue)](${target})`; + + expect(repo.licenseBadge("MIT", false)).toBe(expectedBadge); + }); + }); +}); diff --git a/tests/unit/core/badges.spec.ts b/tests/unit/core/badges.spec.ts index 79080823..4f0483ca 100644 --- a/tests/unit/core/badges.spec.ts +++ b/tests/unit/core/badges.spec.ts @@ -1,4 +1,17 @@ -import { markdownImage, markdownLink } from "@/core/badges"; +import { + buildUrl, + logoParams, + markdownImage, + markdownImageWithLink, + markdownLink, + _dashShieldPath, + _decodeAngleBrackets, + _encodeParam, + _encodeSeparators, + _formatTitle, + _staticDashUrl, + _staticParamsUrl, +} from "@/core/badges"; describe("#markdownLink", () => { it("returns a valid markdown link", () => { @@ -12,20 +25,266 @@ describe("#markdownLink", () => { describe("#markdownImage", () => { it("returns a valid markdown image", () => { - expect(markdownImage("Alt text", "foo.md")).toBe("![Alt text](foo.md)"); + expect(markdownImage("Alt text", "foo.png")).toBe("![Alt text](foo.png)"); - expect(markdownImage("Example", "https://example.com")).toBe( - "![Example](https://example.com)" + expect(markdownImage("Example", "https://example.com/foo.png")).toBe( + "![Example](https://example.com/foo.png)" ); }); it("returns a valid markdown image with hover text", () => { - expect(markdownImage("Alt text", "foo.md", "My hover text")).toBe( - '![Alt text](foo.md "My hover text")' + expect(markdownImage("Alt text", "foo.png", "My hover text")).toBe( + '![Alt text](foo.png "My hover text")' ); + expect(markdownImage("Example", "https://example.com", "My example")).toBe( + '![Example](https://example.com "My example")' + ); + }); +}); + +describe("#markdownImageWithLink", () => { + it("returns a valid tag using all parameters set", () => { + expect( + markdownImageWithLink( + "Alt text", + "foo.png", + "https://example.com", + "My foo" + ) + ).toBe('[![Alt text](foo.png "My foo")](https://example.com)'); + + expect( + markdownImageWithLink("My title", "/example.png", "https://example.com") + ).toBe("[![My title](/example.png)](https://example.com)"); + }); + it("Does not encode special characters", () => { + expect( + markdownImageWithLink( + "My title", + "/example.png", + "https://example.com?foo=bar&fizz_buzz=baz&x>=2" + ) + ).toBe( + "[![My title](/example.png)](https://example.com?foo=bar&fizz_buzz=baz&x>=2)" + ); + }); +}); + +describe("#_encodeSeparators", () => { + it("converts a space to an underscore", () => { + expect(_encodeSeparators("Foo Bar", true)).toBe("Foo_Bar"); + }); + it("will ignore transforming a space", () => { + expect(_encodeSeparators("Foo Bar", false)).toBe("Foo Bar"); + }); + + it("converts a single dash to two", () => { + expect(_encodeSeparators("Foo-Bar", true)).toBe("Foo--Bar"); + }); + + it("converts a single underscore to two", () => { + expect(_encodeSeparators("Foo_Bar", true)).toBe("Foo__Bar"); + }); + + it("converts a mix of space, underscore and a dash correctly", () => { + expect(_encodeSeparators("Foo Bar_Baz-Buzz", true)).toBe( + "Foo_Bar__Baz--Buzz" + ); + }); +}); + +describe("#_decodeAngleBrackets", () => { + it("decodes a left angle bracket", () => { + expect(_decodeAngleBrackets("%3E%3D1")).toBe(">%3D1"); + }); + + it("decodes a right angle bracket", () => { + expect(_decodeAngleBrackets("foo%3C1")).toBe("foo<1"); + }); +}); + +describe("#_encodeParam", () => { + it("converts a space to an underscore", () => { + expect(_encodeParam("Foo Bar")).toBe("Foo_Bar"); + }); + + it("converts a single dash to two", () => { + expect(_encodeParam("Foo-Bar")).toBe("Foo--Bar"); + }); + + it("converts a single underscore to two", () => { + expect(_encodeParam("Foo_Bar")).toBe("Foo__Bar"); + }); + + it("converts a mix of space, underscore and a dash correctly", () => { + expect(_encodeParam("Foo Bar_Baz-Buzz")).toBe("Foo_Bar__Baz--Buzz"); + }); + + // These could appear when putting a URL as a value in the path, so need to be escaped. + it("encodes special characters correctly", () => { + expect(_encodeParam("&")).toBe("%26"); + expect(_encodeParam("/")).toBe("%2F"); + expect(_encodeParam("?")).toBe("%3F"); + }); + + it("encodes a string correctly without converting angle brackets", () => { + expect(_encodeParam(">=3")).toBe(">%3D3"); + expect(_encodeParam("<2")).toBe("<2"); + }); +}); + +describe("#buildUrl", () => { + it("handles empty query params", () => { + expect(buildUrl("http://example.com", {})).toBe("http://example.com/"); + }); + + it("ignores a param which is null", () => { + expect(buildUrl("http://example.com", { foo: "" })).toBe( + "http://example.com/" + ); + }); + + it("adds a single query param", () => { + expect(buildUrl("http://example.com", { foo: "bar" })).toBe( + "http://example.com/?foo=bar" + ); + }); + + it("adds two query params", () => { + expect(buildUrl("http://example.com", { foo: "bar", bar: "bazz" })).toBe( + "http://example.com/?foo=bar&bar=bazz" + ); + }); +}); + +describe("#_formatTitle", () => { + it("formats a message alone", () => { + expect(_formatTitle("", "foo")).toBe("foo"); + }); + + it("formats a label and message together", () => { + expect(_formatTitle("bar", "foo")).toBe("bar - foo"); + }); +}); + +describe("#_dashShieldPath", () => { + it("combines 2 fields", () => { + expect(_dashShieldPath({ message: "Foo", color: "green" })).toBe( + "Foo-green" + ); + }); + + it("combines 2 fields", () => { + expect( + _dashShieldPath({ message: "Foo", color: "green", label: "Bar" }) + ).toBe("Bar-Foo-green"); + }); + + it("combines 2 fields and applies encoding", () => { + expect( + _dashShieldPath({ message: "Foo Bar", color: "green", label: "Baz" }) + ).toBe("Baz-Foo_Bar-green"); + + expect( + _dashShieldPath({ message: "Foo", color: "green", label: "Baz-Buzz" }) + ).toBe("Baz--Buzz-Foo-green"); + + expect( + _dashShieldPath({ label: "Foo", message: ">=1.0.0", color: "green" }) + ).toBe("Foo->%3D1.0.0-green"); + }); +}); + +describe("#logoParams", () => { + it("returns null params", () => { + expect(logoParams(false)).toStrictEqual({}); + }); + + it("returns a large badge", () => { + expect(logoParams(true)).toStrictEqual({ style: "for-the-badge" }); + }); + + it("returns a logo name", () => { + expect(logoParams(false, "foo")).toStrictEqual({ logo: "foo" }); + }); + + it("returns a logo name and logo color", () => { + expect(logoParams(false, "foo", "white")).toStrictEqual({ + logo: "foo", + logoColor: "white", + }); + }); + + it("returns a logo name and logo color for a large badge", () => { + expect(logoParams(true, "foo", "white")).toStrictEqual({ + style: "for-the-badge", + logo: "foo", + logoColor: "white", + }); + }); + + it("ignores a logo color with no logo name", () => { + expect(logoParams(false, "", "white")).toStrictEqual({}); + }); +}); + +describe("#_staticParamsUrl", () => { + it("returns a valid params URL with empty style params", () => { + expect( + _staticParamsUrl( + { + label: "Foo", + message: "Bar", + color: "green", + }, + {} + ) + ).toBe( + "https://img.shields.io/static/v1?label=Foo&message=Bar&color=green" + ); + }); + + it("returns a valid params URL with style params", () => { + expect( + _staticParamsUrl( + { + label: "Foo", + message: "Bar", + color: "green", + }, + { fizz: "buzz" } + ) + ).toBe( + "https://img.shields.io/static/v1?label=Foo&message=Bar&color=green&fizz=buzz" + ); + }); +}); + +describe("#_staticDashUrl", () => { + it("returns a valid dash URL with empty style params", () => { + expect( + _staticDashUrl( + { + label: "Foo", + message: "Bar", + color: "green", + }, + {} + ) + ).toBe("https://img.shields.io/badge/Foo-Bar-green"); + }); + + it("returns a valid dash URL with style params", () => { expect( - markdownImage("Example", "https://example.com", "Go to example") - ).toBe('![Example](https://example.com "Go to example")'); + _staticDashUrl( + { + label: "Foo", + message: "Bar", + color: "green", + }, + { fizz: "buzz" } + ) + ).toBe("https://img.shields.io/badge/Foo-Bar-green?fizz=buzz"); }); }); diff --git a/tests/unit/core/badgesGeneric.spec.ts b/tests/unit/core/badgesGeneric.spec.ts new file mode 100644 index 00000000..649cac7c --- /dev/null +++ b/tests/unit/core/badgesGeneric.spec.ts @@ -0,0 +1,79 @@ +import { genericBadge } from "@/core/badges"; + +/** + * Test that the UI defaults and common choices associated with the Generic view are working. + */ +describe("#genericBadge", () => { + describe("Basic", () => { + it("displays a badge given a message and a color", () => { + expect(genericBadge("", "Bar", "green")).toBe( + "![Bar](https://img.shields.io/badge/Bar-green)" + ); + }); + + it("displays a badge given label, message and a color", () => { + expect(genericBadge("Foo", "Bar", "green")).toBe( + "![Foo - Bar](https://img.shields.io/badge/Foo-Bar-green)" + ); + }); + + it("displays a badge pointing to external link", () => { + const target = "https://example.com"; + + expect(genericBadge("", "Bar", "green", false, target)).toBe( + "[![Bar](https://img.shields.io/badge/Bar-green)](https://example.com)" + ); + + expect( + genericBadge("", "Bar", "green", false, target, "", "", true) + ).toBe( + "[![Bar](https://img.shields.io/static/v1?message=Bar&color=green)](https://example.com)" + ); + }); + + describe("Size", () => { + it("displays a large badge", () => { + const displayLarge = true; + + expect(genericBadge("", "Bar", "green", displayLarge)).toBe( + "![Bar](https://img.shields.io/badge/Bar-green?style=for-the-badge)" + ); + + expect( + genericBadge("", "Bar", "green", displayLarge, "", "", "", true) + ).toBe( + "![Bar](https://img.shields.io/static/v1?message=Bar&color=green&style=for-the-badge)" + ); + }); + }); + }); + + describe("Logo", () => { + it("displays a logo", () => { + const logo = "github"; + + expect(genericBadge("", "Bar", "green", false, "", logo)).toBe( + "![Bar](https://img.shields.io/badge/Bar-green?logo=github)" + ); + + expect(genericBadge("", "Bar", "green", false, "", logo, "", true)).toBe( + "![Bar](https://img.shields.io/static/v1?message=Bar&color=green&logo=github)" + ); + }); + + it("displays a logo with a custom logo color", () => { + const logo = "github", + logoColor = "yellow"; + + expect(genericBadge("", "Bar", "green", false, "", logo, logoColor)).toBe( + "![Bar](https://img.shields.io/badge/Bar-green?logo=github&logoColor=yellow)" + ); + + expect( + genericBadge("", "Bar", "green", false, "", logo, logoColor, true) + ).toBe( + "![Bar](https://img.shields.io/static/v1?message=Bar&color=green&logo=github&logoColor=yellow)" + ); + }); + }); +}); diff --git a/tests/unit/core/packages.spec.ts b/tests/unit/core/packages.spec.ts new file mode 100644 index 00000000..6f146a67 --- /dev/null +++ b/tests/unit/core/packages.spec.ts @@ -0,0 +1,47 @@ +import { REGISTRY } from "@/core/constants"; +import { dependency, nodeVersionBadge } from "@/core/packages"; + +describe("#dependency", () => { + it("returns a standard size static badge for a Node package", () => { + expect(dependency("react", REGISTRY.Node)).toBe( + "[![dependency - react](https://img.shields.io/badge/dependency-react-blue)](https://www.npmjs.com/package/react)" + ); + }); +}); + +describe("#nodeVersionBadge", () => { + const target = "https://www.npmjs.com/package/vue"; + + it("returns a dynamic Node badge", () => { + const imgUrl = + "https://img.shields.io/github/package-json/dependency-version/MichaelCurrin/badge-generator/vue"; + + expect(nodeVersionBadge("MichaelCurrin", "badge-generator", "vue")).toBe( + `[![Package - vue](${imgUrl})](${target})` + ); + }); + + it("returns a dynamic Node badge with a logo and no logo color", () => { + const imgUrl = + "https://img.shields.io/github/package-json/dependency-version/MichaelCurrin/badge-generator/vue?logo=vue.js"; + + expect( + nodeVersionBadge("MichaelCurrin", "badge-generator", "vue", "vue.js") + ).toBe(`[![Package - vue](${imgUrl})](${target})`); + }); + + it("returns a dynamic Node badge with a logo and logo color", () => { + const imgUrl = + "https://img.shields.io/github/package-json/dependency-version/MichaelCurrin/badge-generator/vue?logo=vue.js&logoColor=white"; + + expect( + nodeVersionBadge( + "MichaelCurrin", + "badge-generator", + "vue", + "vue.js", + "white" + ) + ).toBe(`[![Package - vue](${imgUrl})](${target})`); + }); +});