From 02d59e5852f5796d61e8cda6357e91674c079716 Mon Sep 17 00:00:00 2001 From: Pierre Demailly Date: Wed, 11 Jun 2025 21:46:31 +0200 Subject: [PATCH] chore: update repository --- .eslintignore | 2 - .eslintrc | 7 -- .github/workflows/codeql.yml | 10 +- .github/workflows/node.js.yml | 16 +-- .github/workflows/scorecard.yml | 10 +- .gitignore | 3 +- .npmrc | 2 - LICENSE | 2 +- README.md | 21 ++-- eslint.config.mjs | 11 ++ index.js | 107 ++++++++++--------- jsdoc.json | 20 ---- package.json | 90 +++++++--------- src/utils.js | 123 +++++++++++++++++----- test/__snapshots__/test.js.snap | 12 --- test/fixtures/01.js | 19 +++- test/prettyJson.spec.js | 66 ++++++++++++ test/test.js | 60 ----------- test/utils/isPlainObject.spec.js | 74 +++++++++++++ test/utils/maxKeyLength.spec.js | 22 ++++ test/utils/primeColor.spec.js | 34 ++++++ test/utils/shouldArrayReturnLine.spec.js | 42 ++++++++ test/utils/shouldObjectReturnLine.spec.js | 52 +++++++++ 23 files changed, 539 insertions(+), 266 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc create mode 100644 eslint.config.mjs delete mode 100644 jsdoc.json delete mode 100644 test/__snapshots__/test.js.snap create mode 100644 test/prettyJson.spec.js delete mode 100644 test/test.js create mode 100644 test/utils/isPlainObject.spec.js create mode 100644 test/utils/maxKeyLength.spec.js create mode 100644 test/utils/primeColor.spec.js create mode 100644 test/utils/shouldArrayReturnLine.spec.js create mode 100644 test/utils/shouldObjectReturnLine.spec.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 6ec62a7..0000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -/test -/coverage diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 2f31777..0000000 --- a/.eslintrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "@nodesecure/eslint-config", - "parserOptions": { - "sourceType": "module", - "requireConfigFile": false - } -} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 297da21..7f5a090 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -43,15 +43,15 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@1b05615854632b887b69ae1be8cbefe72d3ae423 # v2.6.0 + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Checkout repository - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99 + uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -65,7 +65,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99 + uses: github/codeql-action/autobuild@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -78,6 +78,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 + uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index cbd524e..51ac3ba 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -5,9 +5,11 @@ name: Node.js CI on: push: - branches: [ "master" ] + branches: + - master pull_request: - branches: [ "master" ] + branches: + - master permissions: contents: read @@ -17,17 +19,17 @@ jobs: runs-on: ${{matrix.os}} strategy: matrix: - node-version: [16.x, 18.x, 20.x] - os: [ubuntu-latest, windows-latest, macos-latest] + node-version: [22.x, 24.x] + os: [ubuntu-latest] steps: - name: Harden Runner - uses: step-security/harden-runner@1b05615854632b887b69ae1be8cbefe72d3ae423 # v2.6.0 + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index fd37e5d..171c318 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,17 +32,17 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@1b05615854632b887b69ae1be8cbefe72d3ae423 # v2.6.0 + uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: "Checkout code" - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: results_file: results.sarif results_format: sarif @@ -64,7 +64,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: SARIF file path: results.sarif @@ -72,6 +72,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5 + uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 with: sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 8812f2d..3c55be2 100644 --- a/.gitignore +++ b/.gitignore @@ -57,5 +57,4 @@ typings/ # dotenv environment variables file .env -jsdoc/ -docs/ +.temp diff --git a/.npmrc b/.npmrc index 8fb0f23..43c97e7 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1 @@ package-lock=false -tag-version-prefix="" -message="chore(release): %s" diff --git a/LICENSE b/LICENSE index d9f8d10..5d81c34 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 TopCli +Copyright (c) 2018-2025 TopCli Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ad93f59..6346cc9 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/TopCli/Pretty-JSON/badge?style=for-the-badge)](https://api.securityscorecards.dev/projects/github.com/TopCli/Pretty-JSON) ![build](https://img.shields.io/github/actions/workflow/status/TopCli/Pretty-JSON/node.js.yml?style=for-the-badge) -Stdout JSON in your terminal with colors. This package has been created to stdout clean and beautiful JSON in the SlimIO CLI. +Pretty-print JSON to the terminal with syntax highlighting and structure-aware formatting. ## Requirements -- [Node.js](https://nodejs.org/en/) v16 or higher +- [Node.js](https://nodejs.org/en/) v20 or higher ## Getting Started @@ -25,9 +25,9 @@ $ yarn add @topcli/pretty-json import prettyJSON from "@topcli/pretty-json"; prettyJSON({ - foo: "bar", - hello: "world!", - arr: [1, 2, 3] + foo: "bar", + hello: "world!", + arr: [1, 2, 3] }); ``` @@ -38,14 +38,13 @@ It will produce the following stdout: ## API ### prettyJSON(obj: object): void -Stdout a given JSON Object (Plain Object, Objects Prototype of Object or Array). -## Dependencies +Prints a JSON-compatible object or array to the terminal with syntax highlighting and structured indentation. -|Name|Refactoring|Security Risk|Usage| -|---|---|---|---| -|[@slimio/is](https://github.com/SlimIO/is)|Minor|Low|Type checker| -|[kleur](https://github.com/lukeed/kleur)|Minor|Low|TTY color| +- Supports plain objects, arrays, and nested structures. +- Skips functions and symbols. +- Color-codes data types (strings, numbers, booleans, etc.). +- Outputs readable, formatted JSON — ideal for CLI inspection. ## License MIT diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..8bc142c --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,11 @@ +// Import Third-party Dependencies +import { ESLintConfig } from "@openally/config.eslint"; + +export default [ + ...ESLintConfig, + { + languageOptions: { + sourceType: "module" + } + } +]; diff --git a/index.js b/index.js index b267a7b..0637997 100644 --- a/index.js +++ b/index.js @@ -1,120 +1,129 @@ -// Require Third-party Dependencies -import kleur from "kleur"; -import is from "@slimio/is"; +// Import Node.js Dependencies +import { EOL } from "node:os"; +import { styleText } from "node:util"; +// Import Internal Dependencies +import * as utils from "./src/utils.js"; -// Require Internal Dependencies -import { maxKeyLength, primeColor } from "./src/utils.js"; +function logArray(arr, options = {}) { + const { + depth = 1, + disableEndLine = false + } = options; -// CONSTANTS -const EOL = "\n"; - -/** - * @function logArray - * @param {!Array} arr - * @param {number} [depth=1] - * @param {boolean} [disableEndLine=false] - * @returns {void} - */ -function logArray(arr, depth = 1, disableEndLine = false) { const backStart = depth === 2 ? " " : " ".repeat(depth - 1); const startSpace = depth === 1 ? " " : " ".repeat(depth); const lenMax = arr.length - 1; - const returnLine = arr.length > 10; - const firstIsObject = arr.length > 0 && is.plainObject(arr[0]); + const returnLine = utils.shouldArrayReturnLine(arr, { depth }); + const firstIsObject = arr.length > 0 && utils.isPlainObject(arr[0]); let forceNext = false; if (arr.length === 0) { - process.stdout.write(`${kleur.gray("[]")}${disableEndLine ? "" : EOL}`); + process.stdout.write(`${styleText("gray", "[]")}${disableEndLine ? "" : EOL}`); return; } - process.stdout.write(`${kleur.gray("[")}${EOL}${firstIsObject ? "" : startSpace}`); + if (depth === 1) { + process.stdout.write(EOL); + } + + const shouldReturn = utils.isPlainObject(arr[0]) === false; + process.stdout.write(`${styleText("gray", "[")}${shouldReturn ? EOL : ""}${firstIsObject ? "" : startSpace}`); for (let id = 0; id < arr.length; id++) { const value = arr[id]; - if (is.array(value)) { + if (Array.isArray(value)) { if (id !== 0) { process.stdout.write(startSpace); } - logArray(value, depth + 1, true); + logArray(value, { depth: depth + 1, disableEndLine: true }); forceNext = true; if (id !== lenMax) { - process.stdout.write(kleur.gray(", ")); + process.stdout.write(styleText("gray", ", ")); process.stdout.write(EOL); } } - else if (is.plainObject(value)) { - logObject(value, depth, true); + else if (utils.isPlainObject(value)) { + logObject(value, { + depth, + disableEndLine: utils.shouldObjectReturnLine(value, { depth }), + inArray: true + }); forceNext = true; } else { - // eslint-disable-next-line - if ((returnLine && id > 0) || forceNext) { + if ((returnLine && id > 0) || forceNext) { process.stdout.write(startSpace); forceNext = false; } - process.stdout.write(primeColor(value)(is.string(value) ? `'${value}'` : String(value))); + const isString = typeof value === "string"; + process.stdout.write(utils.primeColor(value)(isString ? `'${value}'` : String(value))); if (id !== lenMax) { - process.stdout.write(kleur.gray(", ")); + process.stdout.write(styleText("gray", ", ")); if (returnLine) { process.stdout.write(EOL); } } } } - process.stdout.write(`${EOL}${backStart}${kleur.gray("]")}${disableEndLine ? "" : EOL}`); + process.stdout.write(`${EOL}${backStart}${styleText("gray", "]")}${disableEndLine ? "" : EOL}`); } -/** - * @function logObject - * @param {!object} obj - * @param {number} [depth=1] - * @param {boolean} [disableEndLine=false] - * @returns {void} - */ -function logObject(obj, depth = 1, disableEndLine = false) { - const betweenSpace = maxKeyLength(obj, 4); +function logObject(obj, options = {}) { + const { + depth = 1, + disableEndLine = false, + inArray = false + } = options; + const betweenSpace = utils.maxKeyLength(obj, 4); const startSpace = depth === 1 ? " " : " ".repeat(depth); const entries = Object.entries(obj); if (entries.length === 0) { - process.stdout.write(`${kleur.gray("{}")}${disableEndLine ? "" : EOL}`); + process.stdout.write(`${styleText("gray", "{}")}${disableEndLine ? "" : EOL}`); return; } for (let id = 0; id < entries.length; id++) { const [key, value] = entries[id]; - if (is.func(value) || is.symbol(value)) { + if (["function", "symbol"].includes(typeof value)) { continue; } if (id === 0) { process.stdout.write(EOL); } - process.stdout.write(kleur.bold(kleur.white(`${startSpace}${key}: ${" ".repeat(betweenSpace - key.length)}`))); - if (is.object(value)) { - (Array.isArray(value) ? logArray : logObject)(value, depth + 1); + process.stdout.write(styleText(["bold", "white"], `${startSpace}${key}: ${" ".repeat(betweenSpace - key.length)}`)); + if (utils.isPlainObject(value)) { + logObject(value, { depth: depth + 1 }); + continue; + } + if (Array.isArray(value)) { + logArray(value, { depth: depth + 1 }); continue; } - process.stdout.write(primeColor(value)(is.string(value) ? `'${value}'` : String(value))); - if (!disableEndLine) { + process.stdout.write(utils.primeColor(value)(typeof value === "string" ? `'${value}'` : String(value))); + if (!disableEndLine && !(inArray && id === entries.length - 1)) { process.stdout.write(EOL); } } } export default function stdoutJSON(obj) { - if (is.object(obj)) { - (Array.isArray(obj) ? logArray : logObject)(obj); - process.stdout.write(EOL); + if (utils.isPlainObject(obj)) { + logObject(obj); + } + else if (Array.isArray(obj)) { + logArray(obj); } else { throw new Error(`${obj} should be object or array.`); } + + process.stdout.write(EOL); } diff --git a/jsdoc.json b/jsdoc.json deleted file mode 100644 index e9e72c6..0000000 --- a/jsdoc.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "opts": { - "encoding": "utf8", - "destination": "./jsdoc/", - "recurse": true, - "template": "node_modules/@escommunity/minami" - }, - "source": { - "include": [ - "index.js", - "./src/utils.js" - ], - "exclude": [ - "node_modules", - "client" - ], - "includePattern": ".+\\.js(doc|x)?$", - "excludePattern": "(^|\\/|\\\\)_" - } -} diff --git a/package.json b/package.json index 2eedca8..05873fd 100644 --- a/package.json +++ b/package.json @@ -1,52 +1,42 @@ { - "name": "@topcli/pretty-json", - "version": "1.3.0", - "description": "Stdout JSON in your terminal", - "main": "./index.js", - "type": "module", - "scripts": { - "start": "node index.js", - "lint": "eslint .", - "prepublishOnly": "pkg-ok", - "test": "node --test test/test.js", - "coverage": "codecov", - "doc": "jsdoc -c ./jsdoc.json -r -R ./README.md -P ./package.json --verbose" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/TopCli/Pretty-JSON.git" - }, - "keywords": [ - "pretty", - "json", - "beautiful", - "format", - "stdout", - "terminal" - ], - "files": [ - "index.js", - "index.d.ts", - "src/utils.js" - ], - "author": "GENTILHOMME Thomas ", - "license": "MIT", - "bugs": { - "url": "https://github.com/TopCli/Pretty-JSON/issues" - }, - "homepage": "https://github.com/TopCli/Pretty-JSON#readme", - "dependencies": { - "@slimio/is": "^2.0.0", - "kleur": "^4.1.5" - }, - "devDependencies": { - "@escommunity/minami": "^1.0.0", - "@nodesecure/eslint-config": "^1.7.0", - "codecov": "^3.7.0", - "eslint": "^8.32.0", - "jsdoc": "^4.0.2" - }, - "engines": { - "node": ">=16" - } + "name": "@topcli/pretty-json", + "version": "1.0.0", + "description": "Stdout JSON in your terminal", + "main": "./index.js", + "type": "module", + "scripts": { + "lint": "eslint .", + "test": "node --test test/**/*.spec.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/TopCli/Pretty-JSON.git" + }, + "keywords": [ + "pretty", + "json", + "beautiful", + "format", + "stdout", + "print", + "cli", + "terminal" + ], + "files": [ + "index.js", + "index.d.ts", + "src/utils.js" + ], + "author": "GENTILHOMME Thomas ", + "license": "MIT", + "bugs": { + "url": "https://github.com/TopCli/Pretty-JSON/issues" + }, + "homepage": "https://github.com/TopCli/Pretty-JSON#readme", + "devDependencies": { + "@openally/config.eslint": "^2.2.0" + }, + "engines": { + "node": ">=20" + } } diff --git a/src/utils.js b/src/utils.js index 2cec40c..e6d198f 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,36 +1,101 @@ -// Require Third-party Dependencies -import kleur from "kleur"; - -/** - * @namespace Utils - */ - -/** - * @function maxKeyLength - * @description get the key with the biggest length (or return the default length if there is none) - * @memberof Utils# - * @param {*} obj - * @param {number} [defaultLength=4] - * @returns {number} - */ -function maxKeyLength(obj, defaultLength = 4) { +// Import Node.js Dependencies +import { styleText } from "node:util"; + +// CONSTANTS +const kMaxInlineArrayLength = 10; + +export function maxKeyLength(obj, defaultLength = 4) { return Object.keys(obj).reduce((prev, curr) => (curr.length > prev ? curr.length : prev), defaultLength); } -/** - * @function primeColor - * @memberof Utils# - * @param {string | boolean | number | symbol | bigint | null | undefined} primitive a primitive value - * @returns {Kleur.color} - */ -function primeColor(primitive) { +export function primeColor(primitive) { switch (typeof primitive) { - case "object": return kleur.gray; - case "number": return kleur.cyan; - case "boolean": return kleur.yellow; - case "string": return kleur.green; - default: return kleur.white; + case "object": + return (...args) => styleText("gray", ...args); + case "number": + return (...args) => styleText("cyan", ...args); + case "boolean": + return (...args) => styleText("yellow", ...args); + case "string": + return (...args) => styleText("green", ...args); + default: + return (...args) => styleText("white", ...args); + } +} + +export function isPlainObject(obj) { + return Object.prototype.toString.call(obj) === "[object Object]"; +} + +export function shouldObjectReturnLine(obj, options = {}) { + const { + depth = 1 + } = options; + + if (Object.values(obj).some((item) => Array.isArray(item) || isPlainObject(item))) { + return true; + } + + if (Object.keys(obj).length < 2) { + return false; + } + + const indentationSpace = " ".repeat(depth * 2); + const objectLength = Object.entries(obj).reduce((acc, [key, value]) => { + const isFnOrSymbol = typeof value === "function" || typeof value === "symbol"; + if (isFnOrSymbol) { + return acc; + } + + if (typeof value === "string") { + // + 2 for quotes + return acc + key.length + value.length + 2; + } + + return acc + key.length + String(value).length; + }, indentationSpace.length); + + const totalLength = indentationSpace.length + objectLength; + + if (totalLength > process.stdout.columns) { + return true; } + + return false; } -export { maxKeyLength, primeColor }; +export function shouldArrayReturnLine(array, options = {}) { + const { + depth = 1 + } = options; + + if (array.length > kMaxInlineArrayLength) { + return true; + } + + if (array.some((item) => Array.isArray(item) || isPlainObject(item))) { + return true; + } + + const indentationSpace = " ".repeat(depth * 2); + const arrayLength = array.reduce((acc, item) => { + const isFnOrSymbol = typeof item === "function" || typeof item === "symbol"; + if (isFnOrSymbol) { + return acc; + } + + if (typeof item === "string") { + // + 2 for quotes + return acc + item.length + 2; + } + + return acc + String(item).length; + }, 0); + const totalLength = indentationSpace.length + arrayLength; + + if (totalLength > process.stdout.columns) { + return true; + } + + return false; +} diff --git a/test/__snapshots__/test.js.snap b/test/__snapshots__/test.js.snap deleted file mode 100644 index c1188e7..0000000 --- a/test/__snapshots__/test.js.snap +++ /dev/null @@ -1,12 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`prettyJSON stdout must be the same 1`] = ` -" - foo: 'bar' - hello: 'world' - arr: [ - 1, 2, 3 - ] - -" -`; diff --git a/test/fixtures/01.js b/test/fixtures/01.js index 047f090..c74ec01 100644 --- a/test/fixtures/01.js +++ b/test/fixtures/01.js @@ -1,8 +1,19 @@ -// Require Internal Dependencies +// Import Internal Dependencies import prettyJSON from "../../index.js"; prettyJSON({ - foo: "bar", - hello: "world", - arr: [1, 2, 3] + foo: "bar", + hello: "world", + arr: [1, 2, 3], + emptyArr: [], + nested: { + foz: "baz", + nestedArr: [ + { + foo: "bar", + hello: "world" + } + ], + nestedEmptyArr: [] + } }); diff --git a/test/prettyJson.spec.js b/test/prettyJson.spec.js new file mode 100644 index 0000000..ccb5569 --- /dev/null +++ b/test/prettyJson.spec.js @@ -0,0 +1,66 @@ +// Import Node.js Dependencies +import { spawn } from "node:child_process"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, it } from "node:test"; +import assert from "node:assert"; +import util from "node:util"; +import { EOL } from "node:os"; + +// Import Internal Dependencies +import prettyJSON from "../index.js"; + +// CONSTANTS +const __dirname = dirname(fileURLToPath(import.meta.url)); + +async function executeNodeScript(path) { + const { stdout } = spawn(process.argv[0], [path]); + let str = ""; + for await (const buf of stdout) { + str += buf; + } + + // eslint-disable-next-line no-unused-expressions + str | 0; + + return util.stripVTControlCharacters(str); +} + +describe("prettyJSON", () => { + it("must be a function", () => { + assert.strictEqual(typeof prettyJSON, "function"); + }); + + it("should generate an error", () => { + const expectedValue = "hello"; + assert.throws(() => prettyJSON(expectedValue), + { + name: "Error", + message: `${expectedValue} should be object or array.` + } + ); + }); + + it("should print object", async() => { + const expectedValue = [ + "", + " foo: 'bar'", + " hello: 'world'", + " arr: [", + " 1, 2, 3", + " ]", + " emptyArr: []", + " nested: ", + " foz: 'baz'", + " nestedArr: [", + " foo: 'bar'", + " hello: 'world'", + " ]", + " nestedEmptyArr: []", + "", + "" + ]; + const stdout = await executeNodeScript(join(__dirname, "fixtures", "01.js")); + assert.strictEqual(stdout, expectedValue.join(EOL)); + }); +}); diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 88f5778..0000000 --- a/test/test.js +++ /dev/null @@ -1,60 +0,0 @@ -// Require Node.js Dependencies -import { spawn } from "node:child_process"; -import { join, dirname } from "node:path"; -import { fileURLToPath } from "node:url"; -import { describe, it } from "node:test"; -import { strictEqual, throws } from "node:assert"; - -// Require Third-party Dependencies -import is from "@slimio/is"; - -// Require Internal Dependencies -import prettyJSON from "../index.js"; - -// CONSTANTS -const __dirname = dirname(fileURLToPath(import.meta.url)); - -async function executeNodeScript(path) { - const { stdout } = spawn(process.argv[0], [path]); - let str = ""; - for await (const buf of stdout) { - str += buf; - } - - // eslint-disable-next-line no-unused-expressions - str | 0; - - return str; -} - -describe("getLocalLang/setLocalLang", () => { - it("prettyJSON must be a function", () => { - strictEqual(is.func(prettyJSON), true); - }); - - it("prettyJSON : should generate an error", () => { - const expectedValue = "hello"; - throws(() => { - prettyJSON(expectedValue), - { - name: "Error", - message: `Payload -> ${expectedValue} should be object or array` - } - }) - }); - - it("prettyJSON stdout must be the same", async() => { - const expectedValue = [ - "", - " foo: 'bar'", - " hello: 'world'", - " arr: [", - " 1, 2, 3", - " ]", - "", - "", - ] - const stdout = await executeNodeScript(join(__dirname, "fixtures", "01.js")); - strictEqual(stdout, expectedValue.join('\n')); - }); -}); diff --git a/test/utils/isPlainObject.spec.js b/test/utils/isPlainObject.spec.js new file mode 100644 index 0000000..0199e78 --- /dev/null +++ b/test/utils/isPlainObject.spec.js @@ -0,0 +1,74 @@ +// Import Node.js Dependencies +import { describe, it } from "node:test"; +import assert from "node:assert"; + +// Import Internal Dependencies +import * as utils from "../../src/utils.js"; + +describe("utils/isPlayObject", () => { + it("should return true for plain objects", () => { + const obj = { key: "value" }; + assert.strictEqual(utils.isPlainObject(obj), true); + }); + + it("should return false for arrays", () => { + const arr = [1, 2, 3]; + assert.strictEqual(utils.isPlainObject(arr), false); + }); + + it("should return false for null", () => { + assert.strictEqual(utils.isPlainObject(null), false); + }); + + it("should return false for functions", () => { + function foo() { + return void 0; + } + assert.strictEqual(utils.isPlainObject(foo), false); + }); + + it("should return false for Date objects", () => { + const date = new Date(); + assert.strictEqual(utils.isPlainObject(date), false); + }); + + it("should return false for RegExp objects", () => { + const regex = /test/; + assert.strictEqual(utils.isPlainObject(regex), false); + }); + + it("should return true for instances of custom classes", () => { + class CustomClass {} + const instance = new CustomClass(); + assert.strictEqual(utils.isPlainObject(instance), true); + }); + + it("should return false for Map objects", () => { + const map = new Map(); + assert.strictEqual(utils.isPlainObject(map), false); + }); + + it("should return false for Set objects", () => { + const set = new Set(); + assert.strictEqual(utils.isPlainObject(set), false); + }); + + it("should return false for Symbol objects", () => { + const symbol = Symbol("test"); + assert.strictEqual(utils.isPlainObject(symbol), false); + }); + + it("should return false for BigInt objects", () => { + const bigInt = BigInt(123); + assert.strictEqual(utils.isPlainObject(bigInt), false); + }); + + it("should return false for undefined", () => { + assert.strictEqual(utils.isPlainObject(undefined), false); + }); + + it("should return true for objects with null prototype", () => { + const obj = Object.create(null); + assert.strictEqual(utils.isPlainObject(obj), true); + }); +}); diff --git a/test/utils/maxKeyLength.spec.js b/test/utils/maxKeyLength.spec.js new file mode 100644 index 0000000..6f5afa6 --- /dev/null +++ b/test/utils/maxKeyLength.spec.js @@ -0,0 +1,22 @@ +// Import Node.js Dependencies +import { describe, it } from "node:test"; +import assert from "node:assert"; + +// Import Internal Dependencies +import * as utils from "../../src/utils.js"; + +describe("utils/maxKeyLength", () => { + it("should return the maximum key length for a given object", () => { + const obj = { + short: "value", + longerKey: "another value" + }; + const maxLength = utils.maxKeyLength(obj); + assert.strictEqual(maxLength, "longerKey".length); + }); + + it("should return default for an empty object", () => { + const maxLength = utils.maxKeyLength({}, 10); + assert.strictEqual(maxLength, 10); + }); +}); diff --git a/test/utils/primeColor.spec.js b/test/utils/primeColor.spec.js new file mode 100644 index 0000000..b8d23ff --- /dev/null +++ b/test/utils/primeColor.spec.js @@ -0,0 +1,34 @@ +// Import Node.js Dependencies +import { describe, it } from "node:test"; +import assert from "node:assert"; +import { styleText } from "node:util"; + +// Import Internal Dependencies +import * as utils from "../../src/utils.js"; + +describe("utils/primeColor", () => { + it("should return gray for objects", () => { + const colorFunc = utils.primeColor({}); + assert.strictEqual(colorFunc("test"), styleText("gray", "test")); + }); + + it("should return cyan for numbers", () => { + const colorFunc = utils.primeColor(42); + assert.strictEqual(colorFunc("test"), styleText("cyan", "test")); + }); + + it("should return yellow for booleans", () => { + const colorFunc = utils.primeColor(true); + assert.strictEqual(colorFunc("test"), styleText("yellow", "test")); + }); + + it("should return green for strings", () => { + const colorFunc = utils.primeColor("hello"); + assert.strictEqual(colorFunc("test"), styleText("green", "test")); + }); + + it("should return white for other types", () => { + const colorFunc = utils.primeColor(undefined); + assert.strictEqual(colorFunc("test"), styleText("white", "test")); + }); +}); diff --git a/test/utils/shouldArrayReturnLine.spec.js b/test/utils/shouldArrayReturnLine.spec.js new file mode 100644 index 0000000..db58869 --- /dev/null +++ b/test/utils/shouldArrayReturnLine.spec.js @@ -0,0 +1,42 @@ +// Import Node.js Dependencies +import { before, describe, it } from "node:test"; +import assert from "node:assert"; + +// Import Internal Dependencies +import * as utils from "../../src/utils.js"; + +describe("utils/shouldArrayReturnLine", () => { + before(() => { + process.stdout.columns = 80; + }); + + it("should return true for array longer than max length", () => { + const arr = Array.from({ length: 15 }, (_, i) => i); + assert.strictEqual(utils.shouldArrayReturnLine(arr), true); + }); + + it("should return true if array contains object", () => { + const arr = [{ key: "value" }]; + assert.strictEqual(utils.shouldArrayReturnLine(arr), true); + }); + + it("should return true if array contains array", () => { + const arr = [[1, 2]]; + assert.strictEqual(utils.shouldArrayReturnLine(arr), true); + }); + + it("should return false for empty array", () => { + const arr = []; + assert.strictEqual(utils.shouldArrayReturnLine(arr), false); + }); + + it("should return true if array items exceed terminal width", () => { + const arr = Array.from({ length: 5 }, (_, i) => `item${i}`.repeat(10)); + assert.strictEqual(utils.shouldArrayReturnLine(arr), true); + }); + + it("should return false if all items fit within terminal width", () => { + const arr = ["item1", "item2"]; + assert.strictEqual(utils.shouldArrayReturnLine(arr), false); + }); +}); diff --git a/test/utils/shouldObjectReturnLine.spec.js b/test/utils/shouldObjectReturnLine.spec.js new file mode 100644 index 0000000..7f3c27a --- /dev/null +++ b/test/utils/shouldObjectReturnLine.spec.js @@ -0,0 +1,52 @@ +// Import Node.js Dependencies +import { before, describe, it } from "node:test"; +import assert from "node:assert"; + +// Import Internal Dependencies +import * as utils from "../../src/utils.js"; + +describe("utils/shouldObjectReturnLine", () => { + before(() => { + process.stdout.columns = 80; + }); + + it("should return false for empty objects", () => { + assert.strictEqual(utils.shouldObjectReturnLine({}), false); + }); + + it("should return true if object contains object", () => { + const obj = { key: { nestedKey: "value" } }; + assert.strictEqual(utils.shouldObjectReturnLine(obj), true); + }); + + it("should return true if object contains array", () => { + const obj = { key: [1, 2, 3] }; + assert.strictEqual(utils.shouldObjectReturnLine(obj), true); + }); + + it("should return false if object contains one key", () => { + const obj = { + key1: "boo".repeat(500) + }; + + assert.strictEqual(utils.shouldObjectReturnLine(obj), false); + }); + + it("should return true if displayed key-values exceed terminal width", () => { + const obj = { + key1: "boo".repeat(30), + key2: "boo".repeat(30) + }; + + assert.strictEqual(utils.shouldObjectReturnLine(obj), true); + }); + + it("should return false if all key-values fit within terminal width", () => { + const obj = { + key1: "value1", + key2: "value2" + }; + + assert.strictEqual(utils.shouldObjectReturnLine(obj), false); + }); +});