├── .circleci └── config.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.yaml ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── stale.yaml ├── .gitignore ├── AUTHORS ├── CHANGES.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── docs └── index.md ├── lib ├── formatio.js └── formatio.test.js ├── mkdocs.yml ├── package-lock.json ├── package.json └── rollup.config.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | references: 4 | x-workdir: &work-dir 5 | working_directory: ~/source 6 | 7 | x-save-workspace: &persist-step 8 | persist_to_workspace: 9 | root: ~/source 10 | paths: 11 | - . 12 | 13 | x-attach: &attach-step 14 | attach_workspace: 15 | at: . 16 | 17 | jobs: 18 | install-dependencies: 19 | <<: *work-dir 20 | docker: 21 | - image: circleci/node:10 22 | environment: 23 | HUSKY_SKIP_INSTALL: 1 24 | steps: 25 | - checkout 26 | - restore_cache: 27 | keys: 28 | - v1-npm-{{ checksum "package-lock.json" }} 29 | - run: 30 | name: Install dependencies 31 | command: | 32 | if [ ! -d node_modules ]; then 33 | npm ci 34 | fi 35 | - save_cache: 36 | key: v1-npm-{{ checksum "package-lock.json" }} 37 | paths: 38 | - node_modules 39 | - *persist-step 40 | 41 | lint: 42 | <<: *work-dir 43 | docker: 44 | - image: node 45 | steps: 46 | - *attach-step 47 | - run: 48 | name: lint 49 | command: npm run lint 50 | 51 | node-10: 52 | docker: 53 | - image: circleci/node:10 54 | steps: 55 | - *attach-step 56 | - run: 57 | name: Test 58 | command: npm test 59 | 60 | node-12: 61 | docker: 62 | - image: circleci/node:12 63 | steps: 64 | - *attach-step 65 | - run: 66 | name: Test with coverage 67 | command: npm run test-check-coverage 68 | - run: 69 | name: Upload coverage report 70 | command: bash <(curl -s https://codecov.io/bash) -F unit -s coverage/lcov.info 71 | 72 | node-13: 73 | docker: 74 | - image: circleci/node:13 75 | steps: 76 | - *attach-step 77 | - run: 78 | name: Test 79 | command: npm test 80 | 81 | workflows: 82 | version: 2 83 | formatio: 84 | jobs: 85 | - install-dependencies 86 | - lint: 87 | requires: 88 | - install-dependencies 89 | - node-10: 90 | requires: 91 | - install-dependencies 92 | - node-12: 93 | requires: 94 | - install-dependencies 95 | - node-13: 96 | requires: 97 | - install-dependencies 98 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; EditorConfig file: http://EditorConfig.org 2 | ; Install the "EditorConfig" plugin into your editor to use 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 4 12 | trim_trailing_whitespace = true 13 | 14 | # Matches the exact files either package.json or .travis.yml 15 | [{package.json, .travis.yml}] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | ; Needed if doing `git add --patch` to edit patches 20 | [*.diff] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | extends: 'eslint-config-sinon' 2 | 3 | env: 4 | browser: true 5 | node: true 6 | 7 | globals: 8 | Map: false 9 | Set: false 10 | Symbol: false 11 | BigInt: false 12 | 13 | plugins: 14 | - ie11 15 | 16 | rules: 17 | strict: [error, 'global'] 18 | 19 | ie11/no-collection-args: error 20 | ie11/no-for-in-const: error 21 | ie11/no-loop-func: warn 22 | ie11/no-weak-collections: error 23 | 24 | overrides: 25 | - files: '*.test.*' 26 | plugins: 27 | - mocha 28 | env: 29 | mocha: true 30 | rules: 31 | max-nested-callbacks: [error, 6] 32 | strict: [error, 'global'] 33 | 34 | mocha/handle-done-callback: error 35 | mocha/no-exclusive-tests: error 36 | mocha/no-global-tests: error 37 | mocha/no-hooks-for-single-case: off 38 | mocha/no-identical-title: error 39 | mocha/no-mocha-arrows: error 40 | mocha/no-nested-tests: error 41 | mocha/no-return-and-callback: error 42 | mocha/no-sibling-hooks: error 43 | mocha/no-skipped-tests: error 44 | mocha/no-top-level-hooks: error 45 | 46 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Contributor Code of Conduct 4 | 5 | Please note that this project is released with a [Contributor Code of Conduct](../CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. 6 | 7 | ## Use EditorConfig 8 | 9 | To save everyone some time, please use [EditorConfig](http://editorconfig.org), so your editor helps make 10 | sure we all use the same encoding, indentation, line endings, etc. 11 | 12 | 13 | ## Compatibility 14 | 15 | This repository follows the [compatibility guidelines of `sinon`](https://github.com/sinonjs/sinon/blob/master/CONTRIBUTING.md#compatibility) 16 | 17 | 18 | ## Style 19 | 20 | This repository follows the [style guidelines of `sinon`](https://github.com/sinonjs/sinon/blob/master/CONTRIBUTING.md#style) 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 9 | 10 | > We understand you have a problem and are in a hurry, but please provide us with some info to make it much more likely for your issue to be understood, worked on and resolved quickly. 11 | 12 | 13 | * library version : _please verify that the bug exists in the latest release_ 14 | * Environment : 15 | * Example URL : 16 | * Other libraries you are using: 17 | 18 | **What did you expect to happen?** 19 | 20 | **What actually happens** 21 | 22 | **How to reproduce** 23 | > Describe *with code* how to reproduce the faulty behaviour, 24 | > or link to code on JSBin or similar 25 | 26 | 27 |
28 | Really long code sample or stacktrace 29 | 30 | If you need to provide a dump of a stack trace or 31 | other lengthy material, such as 80 lines of example code, 32 | please stuff it in a `
` tag such as this 33 | to make the issue more readable. Thanks. 34 |
-------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### Purpose (TL;DR) - mandatory 2 | 7 | 8 | 9 | 12 | 19 | 20 | 21 | 24 | 31 | 32 | #### How to verify - mandatory 33 | 1. Check out this branch 34 | 2. `npm install` 35 | 3. 36 | 37 | #### Checklist for author 38 | 39 | - [ ] `npm run lint` passes 40 | - [ ] References to standard library functions are [cached](https://github.com/sinonjs/sinon/pull/1523). 41 | -------------------------------------------------------------------------------- /.github/stale.yaml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules 3 | .idea 4 | coverage/ 5 | site/ 6 | .nyc_output/ 7 | dist/ 8 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Christian Johansen 2 | Morgan Roderick 3 | Tek Nynja 4 | Phaiax 5 | August Lilleaas 6 | Daniel Wittner 7 | Dominykas Blyžė 8 | Edward Betts 9 | Dave Geddes 10 | Stein Magnus Jodal 11 | Brandon Evans 12 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## 6.0.0 4 | 5 | - [`31bec4c`](https://github.com/sinonjs/formatio/commit/31bec4cc2fedf4b0a0b031e557af3d27228b0bff) 6 | Deprecate package (Morgan Roderick) 7 | 8 | _Released on 2021-01-06._ 9 | 10 | ## 5.0.1 11 | 12 | - [`47a360d`](https://github.com/sinonjs/formatio/commit/47a360d5c5db72ba8ced2d598421bfe105facc69) 13 | Bump @sinonjs/samsam to latest (Morgan Roderick) 14 | > 15 | > This helps dedupe dependencies in Sinon. See https://github.com/sinonjs/sinon/issues/2224 16 | > 17 | 18 | _Released on 2020-02-20._ 19 | 20 | ## 5.0.0 21 | 22 | - [`116aedb`](https://github.com/sinonjs/formatio/commit/116aedb20ca89f5f022633457366afe17b070772) 23 | Drop support for Node 8 (Morgan Roderick) 24 | > 25 | > As can be seen at https://github.com/nodejs/Release, Node 8 reached 26 | > "end" of life on 2019-12-31, and is no longer actively supported. 27 | > 28 | > We will stop testing in Node 8 and start testing in Node 13, which will 29 | > become the next LTS release from April 2020. 30 | > 31 | 32 | _Released on 2020-02-19._ 33 | 34 | ## 4.0.1 35 | 36 | - [`8dae99e`](https://github.com/sinonjs/formatio/commit/8dae99e19fb1f63707669aa18375aac377de92be) 37 | Fix changes.md (Morgan Roderick) 38 | - [`8dde8cb`](https://github.com/sinonjs/formatio/commit/8dde8cb370a2ea54f8d46ff43d1bf69a6ce661c5) 39 | Use '--access public' for the publish command (Morgan Roderick) 40 | 41 | _Released on 2019-12-19._ 42 | 43 | ## 4.0.0 44 | 45 | - [`8693846`](https://github.com/sinonjs/formatio/commit/869384686588e4c71612ce99e4b840842a0e5a46) 46 | add support for formatting maps (#51) (Marc Redemske) 47 | > * add support for formatting maps 48 | > * Update @sinonjs/samsam to version that has isMap method 49 | 50 | _Released on 2019-12-19._ 51 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. 8 | 9 | ## Our Standards 10 | 11 | Examples of behavior that contributes to a positive environment for our community include: 12 | 13 | * Demonstrating empathy and kindness toward other people 14 | * Being respectful of differing opinions, viewpoints, and experiences 15 | * Giving and gracefully accepting constructive feedback 16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 17 | * Focusing on what is best not just for us as individuals, but for the overall community 18 | 19 | Examples of unacceptable behavior include: 20 | 21 | * The use of sexualized language or imagery, and sexual attention or 22 | advances of any kind 23 | * Trolling, insulting or derogatory comments, and personal or political attacks 24 | * Public or private harassment 25 | * Publishing others' private information, such as a physical or email 26 | address, without their explicit permission 27 | * Other conduct which could reasonably be considered inappropriate in a 28 | professional setting 29 | 30 | ## Enforcement Responsibilities 31 | 32 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. 33 | 34 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. 35 | 36 | ## Scope 37 | 38 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. 39 | 40 | ## Enforcement 41 | 42 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement by e-mailing any or all of [Morgan Roderick](mailto:morgan@roderick.dk), [Max Antoni](mailto:mail@maxantoni.de), [Carl-Erik Kopseng](mailto:carlerik@gmail.com). All complaints will be reviewed and investigated promptly and fairly. 43 | 44 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 45 | 46 | ## Enforcement Guidelines 47 | 48 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: 49 | 50 | ### 1. Correction 51 | 52 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. 53 | 54 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. 55 | 56 | ### 2. Warning 57 | 58 | **Community Impact**: A violation through a single incident or series of actions. 59 | 60 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. 61 | 62 | ### 3. Temporary Ban 63 | 64 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. 65 | 66 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. 67 | 68 | ### 4. Permanent Ban 69 | 70 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. 71 | 72 | **Consequence**: A permanent ban from any sort of public interaction within the community. 73 | 74 | ## Attribution 75 | 76 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, 77 | available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 78 | 79 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). 80 | 81 | [homepage]: https://www.contributor-covenant.org 82 | 83 | For answers to common questions about this code of conduct, see the FAQ at 84 | https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The BSD License) 2 | 3 | Copyright (c) 2010-2012, Christian Johansen (christian@cjohansen.no) and 4 | August Lilleaas (august.lilleaas@gmail.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | * Neither the name of Christian Johansen nor the names of his contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # formatio - DEPRECATED 2 | 3 | --- 4 | 5 | This package has been deprecated and will receive no further maintenance. 6 | 7 | If you need this package, please fork it and maintain it yourself. 8 | 9 | --- 10 | 11 | [![Build status](https://secure.travis-ci.org/sinonjs/formatio.svg?branch=master)](http://travis-ci.org/sinonjs/formatio) 12 | [![codecov](https://codecov.io/gh/sinonjs/formatio/branch/master/graph/badge.svg)](https://codecov.io/gh/sinonjs/formatio) 13 | Contributor Covenant 14 | 15 | > The cheesy object formatter 16 | 17 | Pretty formatting of arbitrary JavaScript values. Currently only supports ascii 18 | formatting, suitable for command-line utilities. Like `JSON.stringify`, it 19 | formats objects recursively, but unlike `JSON.stringify`, it can handle 20 | regular expressions, functions, circular objects and more. 21 | 22 | `formatio` is a general-purpose library. It works in browsers (including old 23 | and rowdy ones, like IE6) and Node. It will define itself as an AMD module if 24 | you want it to (i.e. if there's a `define` function available). 25 | 26 | ## Installation 27 | 28 | ```shell 29 | npm install @sinonjs/formatio 30 | ``` 31 | 32 | ## Documentation 33 | 34 | https://sinonjs.github.io/formatio/ 35 | 36 | 37 | ## Backers 38 | 39 | Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/sinon#backer)] 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | ## Sponsors 74 | 75 | Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/sinon#sponsor)] 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | ## Licence 109 | 110 | formatio was released under [BSD-3](LICENSE) 111 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # formatio 2 | 3 | Pretty formatting of arbitrary JavaScript values. Currently only supports ascii 4 | formatting, suitable for command-line utilities. Like `JSON.stringify`, it 5 | formats objects recursively, but unlike `JSON.stringify`, it can handle 6 | regular expressions, functions, circular objects and more. 7 | 8 | `formatio` is a general-purpose library. It works in browsers (including old 9 | and rowdy ones, like IE6) and Node. If you need to use it with AMD or as a global, then there's a UMD version in `dist/`. 10 | 11 | ## Installation 12 | 13 | ```shell 14 | npm install @sinonjs/formatio 15 | ``` 16 | 17 | 18 | ## `formatio.ascii` API 19 | 20 | `formatio.ascii` can take any JavaScript object and format it nicely as plain 21 | text. It uses the helper functions described below to format different types of 22 | objects. 23 | 24 | 25 | ### `formatio.ascii(object)` 26 | 27 | `object` can be any kind of object, including DOM elements. 28 | 29 | 30 | **Simple object** 31 | 32 | ```javascript 33 | var formatio = require("@sinonjs/formatio"); 34 | 35 | var object = { name: "Christian" }; 36 | console.log(formatio.ascii(object)); 37 | 38 | // Outputs: 39 | // { name: "Christian" } 40 | ``` 41 | 42 | 43 | **Complex object** 44 | 45 | ```javascript 46 | var formatio = require("@sinonjs/formatio"); 47 | 48 | var developer = { 49 | name: "Christian", 50 | interests: ["Programming", "Guitar", "TV"], 51 | 52 | location: { 53 | language: "Norway", 54 | city: "Oslo", 55 | 56 | getLatLon: function getLatLon(callback) { 57 | // ... 58 | }, 59 | 60 | distanceTo: function distanceTo(location) { 61 | } 62 | }, 63 | 64 | speak: function () { 65 | return "Oh hi!"; 66 | } 67 | }; 68 | 69 | console.log(formatio.ascii(developer)); 70 | 71 | // Outputs: 72 | // { 73 | // interests: ["Programming", "Guitar", "TV"], 74 | // location: { 75 | // city: "Oslo", 76 | // distanceTo: function distanceTo() {}, 77 | // getLatLon: function getLatLon() {}, 78 | // language: "Norway" 79 | // }, 80 | // name: "Christian", 81 | // speak: function () {} 82 | // } 83 | ``` 84 | 85 | 86 | **Custom constructor** 87 | 88 | If the object to format is not a generic `Object` object, **formatio** 89 | displays the type of object (i.e. name of constructor). Set the 90 | `excludeConstructors` (see below) property to control what constructors to 91 | include in formatted output. 92 | 93 | ```javascript 94 | var formatio = require("@sinonjs/formatio"); 95 | 96 | function Person(name) { this.name = name; } 97 | 98 | var dude = new Person("Dude"); 99 | console.log(format.ascii(dude)); 100 | 101 | // Outputs: 102 | // [Person] { name: "Dude" } 103 | ``` 104 | 105 | 106 | **DOM elements** 107 | 108 | DOM elements are formatted as abbreviated HTML source. 20 characters of 109 | `innerHTML` is included, and if the content is longer, it is truncated with 110 | `"[...]"`. Future editions will add the possibility to format nested markup 111 | structures. 112 | 113 | ```javascript 114 | var p = document.createElement("p"); 115 | p.id = "sample"; 116 | p.className = "notice"; 117 | p.setAttribute("data-custom", "42"); 118 | p.innerHTML = "Hey there, here's some text for ya there buddy"; 119 | 120 | console.log(formatio.ascii(p)); 121 | 122 | // Outputs 123 | // <p id="sample" class="notice" data-custom="42">Hey there, here's so[...]</p> 124 | ``` 125 | 126 | 127 | ### `formatio.ascii.func(func)` 128 | 129 | Formats a function like `"function [name]() {}"`. The name is retrieved from 130 | `formatio.functionName`. 131 | 132 | 133 | ### `formatio.ascii.array(array)` 134 | 135 | Formats an array as `"[item1, item2, item3]"` where each item is formatted 136 | with `formatio.ascii`. Circular references are represented in the resulting 137 | string as `"[Circular]"`. 138 | 139 | 140 | ### `formatio.ascii.object(object)` 141 | 142 | Formats all properties of the object with `formatio.ascii`. If the object can 143 | be fully represented in 80 characters, it's formatted in one line. Otherwise, 144 | it's nicely indented over as many lines as necessary. Circular references are 145 | represented by `"[Circular]"`. 146 | 147 | Objects created with custom constructors will be formatted as 148 | `"[ConstructorName] { ... }"`. Set the `excludeConstructors` property to 149 | control what constructors are included in the output like this. 150 | 151 | 152 | ### `formatio.ascii.element(element)` 153 | 154 | Formats a DOM element as HTML source. The tag name is represented in lower-case 155 | and all attributes and their values are included. The element's content is 156 | included, up to 20 characters. If the length exceeds 20 characters, it's 157 | truncated with a `"[...]"`. 158 | 159 | 160 | ### `formatio.functionName(func)` 161 | 162 | Guesses a function's name. If the function defines the `displayName` property 163 | (used by `some debugging tools `_) it is 164 | preferred. If it is not found, the `name` property is tried. If no name can be 165 | found this way, an attempt is made to find the function name by looking at the 166 | function's `toString()` representation. 167 | 168 | 169 | ### `formatio.constructorName(object)` 170 | 171 | Attempts to guess the name of the constructor that created the object. It does 172 | so by getting the name of `object.constructor` using `functionName`. If a 173 | name is found, `excludeConstructors` is consulted. If the constructor name 174 | matches any of these elements, an empty string is returned, otherwise the name 175 | is returned. 176 | 177 | 178 | ## `formatio.ascii` properties 179 | 180 | ### `quoteStrings(true)` 181 | 182 | Whether or not to quote simple strings. When set to `false`, simple strings 183 | are not quoted. Strings in arrays and objects will still be quoted, but 184 | `ascii("Some string")` will not gain additional quotes. 185 | 186 | ### `limitChildrenCount(number)` 187 | 188 | This property allows to limit the number of printed array elements or object 189 | properties. When set to 0, all elements will be included in output, any number 190 | greater than zero will set the limit to that number. 191 | 192 | ### `excludeConstructors (["Object", /^.$/])` 193 | 194 | An array of strings and/or regular expressions naming constructors that should 195 | be stripped from the formatted output. The default value skips objects created 196 | by `Object` and constructors that have one character names (which are 197 | typically used in `Object.create` shims). 198 | 199 | While you can set this property directly on `formatio.ascii`, it is 200 | recommended to create an instance of `formatio.ascii` and override the 201 | property on that object. 202 | 203 | **Strings** represent constructor names that should not be represented in the 204 | formatted output. **Regular expressions** are tested against constructor names 205 | when formatting. If the expression is a match, the constructor name is not 206 | included in the formatted output. 207 | 208 | ```javascript 209 | function Person(name) { 210 | this.name = name; 211 | } 212 | 213 | var person = new Person("Chris"); 214 | console.log(formatio.ascii(person)); 215 | 216 | // Outputs 217 | // [Person] { name: "Chris" } 218 | 219 | var formatter = Object.create(formatio); 220 | formatter.excludeConstructors = ["Object", /^.$/, "Person"]; 221 | console.log(formatter.ascii(person)); 222 | 223 | // Outputs 224 | // { name: "Chris" } 225 | 226 | // Global overwrite, generally not recommended 227 | formatio.excludeConstructors = ["Object", /^.$/, "Person"]; 228 | console.log(formatio.ascii(person)); 229 | 230 | // Outputs 231 | // { name: "Chris" } 232 | ``` 233 | -------------------------------------------------------------------------------- /lib/formatio.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var samsam = require("@sinonjs/samsam"); 4 | var functionName = require("@sinonjs/commons").functionName; 5 | var typeOf = require("@sinonjs/commons").typeOf; 6 | 7 | var formatio = { 8 | excludeConstructors: ["Object", /^.$/], 9 | quoteStrings: true, 10 | limitChildrenCount: 0 11 | }; 12 | 13 | var specialObjects = []; 14 | /* istanbul ignore else */ 15 | if (typeof global !== "undefined") { 16 | specialObjects.push({ object: global, value: "[object global]" }); 17 | } 18 | if (typeof document !== "undefined") { 19 | specialObjects.push({ 20 | object: document, 21 | value: "[object HTMLDocument]" 22 | }); 23 | } 24 | if (typeof window !== "undefined") { 25 | specialObjects.push({ object: window, value: "[object Window]" }); 26 | } 27 | 28 | function constructorName(f, object) { 29 | var name = functionName(object && object.constructor); 30 | var excludes = f.excludeConstructors || formatio.excludeConstructors; 31 | 32 | var i, l; 33 | for (i = 0, l = excludes.length; i < l; ++i) { 34 | if (typeof excludes[i] === "string" && excludes[i] === name) { 35 | return ""; 36 | } else if (excludes[i].test && excludes[i].test(name)) { 37 | return ""; 38 | } 39 | } 40 | 41 | return name; 42 | } 43 | 44 | function isCircular(object, objects) { 45 | if (typeof object !== "object") { 46 | return false; 47 | } 48 | var i, l; 49 | for (i = 0, l = objects.length; i < l; ++i) { 50 | if (objects[i] === object) { 51 | return true; 52 | } 53 | } 54 | return false; 55 | } 56 | 57 | // eslint-disable-next-line complexity 58 | function ascii(f, object, processed, indent) { 59 | if (typeof object === "string") { 60 | if (object.length === 0) { 61 | return "(empty string)"; 62 | } 63 | var qs = f.quoteStrings; 64 | var quote = typeof qs !== "boolean" || qs; 65 | // eslint-disable-next-line quotes 66 | return processed || quote ? '"' + object + '"' : object; 67 | } 68 | 69 | if (typeof object === "symbol") { 70 | return object.toString(); 71 | } 72 | 73 | if (typeof object === "function" && !(object instanceof RegExp)) { 74 | return ascii.func(object); 75 | } 76 | 77 | // eslint supports bigint as of version 6.0.0 78 | // https://github.com/eslint/eslint/commit/e4ab0531c4e44c23494c6a802aa2329d15ac90e5 79 | // eslint-disable-next-line 80 | if (typeOf(object) === "bigint") { 81 | return object.toString(); 82 | } 83 | 84 | var internalProcessed = processed || []; 85 | 86 | if (isCircular(object, internalProcessed)) { 87 | return "[Circular]"; 88 | } 89 | 90 | if (typeOf(object) === "array") { 91 | return ascii.array.call(f, object, internalProcessed); 92 | } 93 | 94 | if (!object) { 95 | return String(1 / object === -Infinity ? "-0" : object); 96 | } 97 | if (samsam.isElement(object)) { 98 | return ascii.element(object); 99 | } 100 | 101 | if ( 102 | typeof object.toString === "function" && 103 | object.toString !== Object.prototype.toString 104 | ) { 105 | return object.toString(); 106 | } 107 | 108 | var i, l; 109 | for (i = 0, l = specialObjects.length; i < l; i++) { 110 | if (object === specialObjects[i].object) { 111 | return specialObjects[i].value; 112 | } 113 | } 114 | 115 | if (samsam.isSet(object)) { 116 | return ascii.set.call(f, object, internalProcessed); 117 | } 118 | 119 | if (object instanceof Map) { 120 | return ascii.map.call(f, object, internalProcessed); 121 | } 122 | 123 | return ascii.object.call(f, object, internalProcessed, indent); 124 | } 125 | 126 | ascii.func = function(func) { 127 | var funcName = functionName(func) || ""; 128 | return "function " + funcName + "() {}"; 129 | }; 130 | 131 | function delimit(str, delimiters) { 132 | var delims = delimiters || ["[", "]"]; 133 | return delims[0] + str + delims[1]; 134 | } 135 | 136 | ascii.array = function(array, processed, delimiters) { 137 | processed.push(array); 138 | var pieces = []; 139 | var i, l; 140 | l = 141 | this.limitChildrenCount > 0 142 | ? Math.min(this.limitChildrenCount, array.length) 143 | : array.length; 144 | 145 | for (i = 0; i < l; ++i) { 146 | pieces.push(ascii(this, array[i], processed)); 147 | } 148 | 149 | if (l < array.length) { 150 | pieces.push("[... " + (array.length - l) + " more elements]"); 151 | } 152 | 153 | return delimit(pieces.join(", "), delimiters); 154 | }; 155 | 156 | ascii.set = function(set, processed) { 157 | return ascii.array.call(this, Array.from(set), processed, ["Set {", "}"]); 158 | }; 159 | 160 | ascii.map = function(map, processed) { 161 | return ascii.array.call(this, Array.from(map), processed, ["Map [", "]"]); 162 | }; 163 | 164 | function getSymbols(object) { 165 | if (samsam.isArguments(object)) { 166 | return []; 167 | } 168 | 169 | /* istanbul ignore else */ 170 | if (typeof Object.getOwnPropertySymbols === "function") { 171 | return Object.getOwnPropertySymbols(object); 172 | } 173 | 174 | /* istanbul ignore next: This is only for IE, since getOwnPropertySymbols 175 | * does not exist on Object there 176 | */ 177 | return []; 178 | } 179 | 180 | ascii.object = function(object, processed, indent) { 181 | processed.push(object); 182 | var internalIndent = indent || 0; 183 | var pieces = []; 184 | var properties = Object.keys(object) 185 | .sort() 186 | .concat(getSymbols(object)); 187 | var length = 3; 188 | var prop, str, obj, i, k, l; 189 | l = 190 | this.limitChildrenCount > 0 191 | ? Math.min(this.limitChildrenCount, properties.length) 192 | : properties.length; 193 | 194 | for (i = 0; i < l; ++i) { 195 | prop = properties[i]; 196 | obj = object[prop]; 197 | 198 | if (isCircular(obj, processed)) { 199 | str = "[Circular]"; 200 | } else { 201 | str = ascii(this, obj, processed, internalIndent + 2); 202 | } 203 | 204 | str = 205 | (typeof prop === "string" && /\s/.test(prop) 206 | ? // eslint-disable-next-line quotes 207 | '"' + prop + '"' 208 | : prop.toString()) + 209 | ": " + 210 | str; 211 | length += str.length; 212 | pieces.push(str); 213 | } 214 | 215 | var cons = constructorName(this, object); 216 | var prefix = cons ? "[" + cons + "] " : ""; 217 | var is = ""; 218 | for (i = 0, k = internalIndent; i < k; ++i) { 219 | is += " "; 220 | } 221 | 222 | if (l < properties.length) { 223 | pieces.push("[... " + (properties.length - l) + " more elements]"); 224 | } 225 | 226 | if (length + internalIndent > 80) { 227 | return ( 228 | prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + is + "}" 229 | ); 230 | } 231 | return prefix + "{ " + pieces.join(", ") + " }"; 232 | }; 233 | 234 | ascii.element = function(element) { 235 | var tagName = element.tagName.toLowerCase(); 236 | var attrs = element.attributes; 237 | var pairs = []; 238 | var attr, attrName, i, l, val; 239 | 240 | for (i = 0, l = attrs.length; i < l; ++i) { 241 | attr = attrs.item(i); 242 | attrName = attr.nodeName.toLowerCase().replace("html:", ""); 243 | val = attr.nodeValue; 244 | if (attrName !== "contenteditable" || val !== "inherit") { 245 | if (val) { 246 | // eslint-disable-next-line quotes 247 | pairs.push(attrName + '="' + val + '"'); 248 | } 249 | } 250 | } 251 | 252 | var formatted = "<" + tagName + (pairs.length > 0 ? " " : ""); 253 | // SVG elements have undefined innerHTML 254 | var content = element.innerHTML || ""; 255 | 256 | if (content.length > 20) { 257 | content = content.substr(0, 20) + "[...]"; 258 | } 259 | 260 | var res = 261 | formatted + pairs.join(" ") + ">" + content + ""; 262 | 263 | return res.replace(/ contentEditable="inherit"/, ""); 264 | }; 265 | 266 | function Formatio(options) { 267 | // eslint-disable-next-line guard-for-in 268 | for (var opt in options) { 269 | this[opt] = options[opt]; 270 | } 271 | } 272 | 273 | Formatio.prototype = { 274 | functionName: functionName, 275 | 276 | configure: function(options) { 277 | return new Formatio(options); 278 | }, 279 | 280 | constructorName: function(object) { 281 | return constructorName(this, object); 282 | }, 283 | 284 | ascii: function(object, processed, indent) { 285 | return ascii(this, object, processed, indent); 286 | } 287 | }; 288 | 289 | module.exports = Formatio.prototype; 290 | -------------------------------------------------------------------------------- /lib/formatio.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var jsdom = require("jsdom-global")("", { url: "http://localhost" }); 4 | var assert = require("@sinonjs/referee").assert; 5 | var refute = require("@sinonjs/referee").refute; 6 | var formatio = require("./formatio"); 7 | 8 | var namesAnonymousFunctions = (function() { 9 | // eslint-disable-next-line no-empty-function 10 | var f = function() {}; 11 | 12 | return f.name === "f"; 13 | })(); 14 | 15 | function range(size) { 16 | var array = []; 17 | 18 | for (var i = 0; i < size; i++) { 19 | array[i] = i; 20 | } 21 | 22 | return array; 23 | } 24 | 25 | function getObjectWithManyProperties(size) { 26 | var object = {}; 27 | 28 | for (var i = 0; i < size; i++) { 29 | object[i.toString()] = i; 30 | } 31 | 32 | return object; 33 | } 34 | 35 | describe("formatio.ascii", function() { 36 | it("formats strings with quotes", function() { 37 | // eslint-disable-next-line quotes 38 | assert.equals(formatio.ascii("A string"), '"A string"'); 39 | }); 40 | 41 | it("formats 0-length strings in a special way", function() { 42 | assert.equals(formatio.ascii(""), "(empty string)"); 43 | }); 44 | 45 | it("formats booleans without quotes", function() { 46 | assert.equals(formatio.ascii(true), "true"); 47 | assert.equals(formatio.ascii(false), "false"); 48 | }); 49 | 50 | it("formats null and undefined without quotes", function() { 51 | assert.equals(formatio.ascii(null), "null"); 52 | assert.equals(formatio.ascii(undefined), "undefined"); 53 | }); 54 | 55 | it("formats numbers without quotes", function() { 56 | assert.equals(formatio.ascii(3), "3"); 57 | assert.equals(formatio.ascii(3987.56), "3987.56"); 58 | assert.equals(formatio.ascii(-980.0), "-980"); 59 | assert.equals(formatio.ascii(NaN), "NaN"); 60 | assert.equals(formatio.ascii(Infinity), "Infinity"); 61 | assert.equals(formatio.ascii(-Infinity), "-Infinity"); 62 | assert.equals(formatio.ascii(-0), "-0"); 63 | }); 64 | 65 | it("formats regexp using toString", function() { 66 | assert.equals(formatio.ascii(/[a-zA-Z0-9]+\.?/), "/[a-zA-Z0-9]+\\.?/"); 67 | }); 68 | 69 | it("formats functions with name", function() { 70 | // eslint-disable-next-line no-empty-function 71 | var fn = function doIt() {}; 72 | assert.equals(formatio.ascii(fn), "function doIt() {}"); 73 | }); 74 | 75 | it("formats functions without name", function() { 76 | assert.equals( 77 | // eslint-disable-next-line no-empty-function 78 | formatio.ascii(function() {}), 79 | "function () {}" 80 | ); 81 | }); 82 | 83 | it("formats functions with display name", function() { 84 | // eslint-disable-next-line no-empty-function 85 | function doIt() {} 86 | doIt.displayName = "ohHai"; 87 | 88 | assert.equals(formatio.ascii(doIt), "function ohHai() {}"); 89 | }); 90 | 91 | it("shortens functions with long bodies", function() { 92 | function doIt() { 93 | var i; 94 | // eslint-disable-next-line no-unused-vars, no-empty-function 95 | function hey() {} 96 | for (i = 0; i < 10; i++) { 97 | // eslint-disable-next-line no-console 98 | console.log(i); 99 | } 100 | } 101 | 102 | assert.equals(formatio.ascii(doIt), "function doIt() {}"); 103 | }); 104 | 105 | it("formats functions with no name or display name", function() { 106 | // eslint-disable-next-line no-empty-function 107 | function doIt() {} 108 | 109 | Object.defineProperty(doIt, "name", { 110 | value: "", 111 | writable: false 112 | }); 113 | 114 | assert.equals(formatio.ascii(doIt), "function doIt() {}"); 115 | }); 116 | 117 | it("formats arrays", function() { 118 | function ohNo() { 119 | return "Oh yes!"; 120 | } 121 | 122 | var array = ["String", 123, /a-z/, null]; 123 | 124 | var str = formatio.ascii(array); 125 | // eslint-disable-next-line quotes 126 | assert.equals(str, '["String", 123, /a-z/, null]'); 127 | 128 | str = formatio.ascii([ohNo, array]); 129 | assert.equals( 130 | str, 131 | // eslint-disable-next-line quotes 132 | '[function ohNo() {}, ["String", 123, /a-z/, null]]' 133 | ); 134 | }); 135 | 136 | it("does not trip on circular arrays", function() { 137 | var array = ["String", 123, /a-z/]; 138 | array.push(array); 139 | 140 | var str = formatio.ascii(array); 141 | // eslint-disable-next-line quotes 142 | assert.equals(str, '["String", 123, /a-z/, [Circular]]'); 143 | }); 144 | 145 | describe("limit formatted array length", function() { 146 | it("should stop at given limit", function() { 147 | var configuredFormatio = formatio.configure({ 148 | limitChildrenCount: 30 149 | }); 150 | var str = configuredFormatio.ascii(range(300)); 151 | 152 | refute.contains(str, "30"); 153 | assert.contains(str, "29"); 154 | assert.contains(str, "[... 270 more elements]"); 155 | }); 156 | 157 | it("should only format as many elements as exists", function() { 158 | var configuredFormatio = formatio.configure({ 159 | limitChildrenCount: 30 160 | }); 161 | var str = configuredFormatio.ascii(range(10)); 162 | 163 | refute.contains(str, "10"); 164 | assert.contains(str, "9"); 165 | refute.contains(str, "undefined"); 166 | refute.contains(str, "[..."); 167 | }); 168 | 169 | it("should format all array elements if no config is used", function() { 170 | var str = formatio.ascii(range(300)); 171 | 172 | assert.contains(str, "100"); 173 | assert.contains(str, "299]"); 174 | refute.contains(str, "[..."); 175 | }); 176 | }); 177 | 178 | describe("limit count of formatted object properties", function() { 179 | it("should stop at given limit", function() { 180 | var configured = formatio.configure({ 181 | limitChildrenCount: 30 182 | }); 183 | var str = configured.ascii(getObjectWithManyProperties(300)); 184 | 185 | // returned formation may not be in the original order 186 | assert.equals(30 + 3, str.split("\n").length); 187 | assert.contains(str, "[... 270 more elements]"); 188 | }); 189 | 190 | it("should only format as many properties as exists", function() { 191 | var configured = formatio.configure({ 192 | limitChildrenCount: 30 193 | }); 194 | var str = configured.ascii(getObjectWithManyProperties(10)); 195 | 196 | refute.contains(str, "10"); 197 | assert.contains(str, "9"); 198 | refute.contains(str, "undefined"); 199 | refute.contains(str, "[..."); 200 | }); 201 | 202 | it("should format all properties if no config is used", function() { 203 | var str = formatio.ascii(getObjectWithManyProperties(300)); 204 | 205 | assert.equals(300 + 2, str.split("\n").length); 206 | }); 207 | }); 208 | 209 | it("formats object", function() { 210 | var object = { 211 | id: 42, 212 | // eslint-disable-next-line no-empty-function 213 | hello: function() {}, 214 | prop: "Some", 215 | more: "properties", 216 | please: "Gimme some more", 217 | "oh hi": 42, 218 | seriously: "many properties" 219 | }; 220 | object[Symbol("key")] = Symbol("value"); 221 | 222 | var expectedFunctionString = namesAnonymousFunctions 223 | ? "hello: function hello() {}" 224 | : "hello: function () {}"; 225 | 226 | var expected = 227 | "{\n " + 228 | expectedFunctionString + 229 | ",\n id: 42,\n " + 230 | // eslint-disable-next-line quotes 231 | 'more: "properties",\n "oh hi": 42,\n please: ' + 232 | // eslint-disable-next-line quotes 233 | '"Gimme some more",\n prop: "Some",\n' + 234 | // eslint-disable-next-line quotes 235 | ' seriously: "many properties",\n' + 236 | " Symbol(key): Symbol(value)\n}"; 237 | 238 | assert.equals(formatio.ascii(object), expected); 239 | }); 240 | 241 | it("formats short object on one line", function() { 242 | var object = { 243 | id: 42, 244 | // eslint-disable-next-line no-empty-function 245 | hello: function() {}, 246 | prop: "Some" 247 | }; 248 | 249 | var expectedFunctionString = namesAnonymousFunctions 250 | ? "hello: function hello() {}" 251 | : "hello: function () {}"; 252 | 253 | var expected = 254 | // eslint-disable-next-line quotes 255 | "{ " + expectedFunctionString + ', id: 42, prop: "Some" }'; 256 | assert.equals(formatio.ascii(object), expected); 257 | }); 258 | 259 | it("formats object with a non-function toString", function() { 260 | var object = { toString: 42 }; 261 | assert.equals(formatio.ascii(object), "{ toString: 42 }"); 262 | }); 263 | 264 | it("formats nested object", function() { 265 | var object = { 266 | id: 42, 267 | // eslint-disable-next-line no-empty-function 268 | hello: function() {}, 269 | prop: "Some", 270 | obj: { 271 | num: 23, 272 | string: "Here you go you little mister" 273 | } 274 | }; 275 | 276 | var expectedFunctionString = namesAnonymousFunctions 277 | ? "hello: function hello() {}" 278 | : "hello: function () {}"; 279 | 280 | var expected = 281 | "{\n " + 282 | expectedFunctionString + 283 | ",\n id: 42,\n obj" + 284 | // eslint-disable-next-line quotes 285 | ': { num: 23, string: "Here you go you little mister"' + 286 | // eslint-disable-next-line quotes 287 | ' },\n prop: "Some"\n}'; 288 | 289 | assert.equals(formatio.ascii(object), expected); 290 | }); 291 | 292 | it("includes constructor if known and not Object", function() { 293 | function Person(name) { 294 | this.name = name; 295 | } 296 | 297 | var person = new Person("Christian"); 298 | 299 | // eslint-disable-next-line quotes 300 | assert.equals(formatio.ascii(person), '[Person] { name: "Christian" }'); 301 | }); 302 | 303 | it("does not include one letter constructors", function() { 304 | function F(name) { 305 | this.name = name; 306 | } 307 | 308 | var person = new F("Christian"); 309 | 310 | // eslint-disable-next-line quotes 311 | assert.equals(formatio.ascii(person), '{ name: "Christian" }'); 312 | }); 313 | 314 | it("includes one letter constructors when configured so", function() { 315 | function C(name) { 316 | this.name = name; 317 | } 318 | 319 | var person = new C("Christian"); 320 | var formatter = formatio.configure({ excludeConstructors: [] }); 321 | 322 | // eslint-disable-next-line quotes 323 | assert.equals(formatter.ascii(person), '[C] { name: "Christian" }'); 324 | }); 325 | 326 | it("excludes constructors when configured to do so", function() { 327 | function Person(name) { 328 | this.name = name; 329 | } 330 | 331 | var person = new Person("Christian"); 332 | var formatter = formatio.configure({ excludeConstructors: ["Person"] }); 333 | 334 | // eslint-disable-next-line quotes 335 | assert.equals(formatter.ascii(person), '{ name: "Christian" }'); 336 | }); 337 | 338 | it("excludes constructors by pattern when configured so", function() { 339 | function Person(name) { 340 | this.name = name; 341 | } 342 | function Ninja(name) { 343 | this.name = name; 344 | } 345 | function Pervert(name) { 346 | this.name = name; 347 | } 348 | 349 | var person = new Person("Christian"); 350 | var ninja = new Ninja("Haruhachi"); 351 | var pervert = new Pervert("Mr. Garrison"); 352 | var formatter = formatio.configure({ excludeConstructors: [/^Per/] }); 353 | 354 | // eslint-disable-next-line quotes 355 | assert.equals(formatter.ascii(person), '{ name: "Christian" }'); 356 | 357 | // eslint-disable-next-line quotes 358 | assert.equals(formatter.ascii(ninja), '[Ninja] { name: "Haruhachi" }'); 359 | 360 | // eslint-disable-next-line quotes 361 | assert.equals(formatter.ascii(pervert), '{ name: "Mr. Garrison" }'); 362 | }); 363 | 364 | it("excludes constructors when run on other objects", function() { 365 | function Person(name) { 366 | this.name = name; 367 | } 368 | 369 | var person = new Person("Christian"); 370 | var formatter = { ascii: formatio.ascii }; 371 | formatter.excludeConstructors = ["Person"]; 372 | 373 | // eslint-disable-next-line quotes 374 | assert.equals(formatter.ascii(person), '{ name: "Christian" }'); 375 | }); 376 | 377 | it("excludes default constructors when run on other objects", function() { 378 | var person = { name: "Christian" }; 379 | var formatter = { ascii: formatio.ascii }; 380 | 381 | // eslint-disable-next-line quotes 382 | assert.equals(formatter.ascii(person), '{ name: "Christian" }'); 383 | }); 384 | 385 | it("does not trip on circular formatting", function() { 386 | var object = {}; 387 | object.foo = object; 388 | 389 | assert.equals(formatio.ascii(object), "{ foo: [Circular] }"); 390 | }); 391 | 392 | it("does not trip on indirect circular formatting", function() { 393 | var object = { someProp: {} }; 394 | object.someProp.foo = object; 395 | 396 | assert.equals( 397 | formatio.ascii(object), 398 | "{ someProp: { foo: [Circular] } }" 399 | ); 400 | }); 401 | 402 | it("formats nested array nicely", function() { 403 | var object = { people: ["Chris", "August"] }; 404 | 405 | assert.equals( 406 | formatio.ascii(object), 407 | // eslint-disable-next-line quotes 408 | '{ people: ["Chris", "August"] }' 409 | ); 410 | }); 411 | 412 | it("does not rely on object's hasOwnProperty", function() { 413 | // Create object with no "own" properties to get past 414 | // Object.keys test and no .hasOwnProperty() function 415 | // eslint-disable-next-line no-empty-function 416 | var Obj = function() {}; 417 | Obj.prototype = { hasOwnProperty: undefined }; 418 | var object = new Obj(); 419 | 420 | assert.equals(formatio.ascii(object), "{ }"); 421 | }); 422 | 423 | it("handles cyclic structures", function() { 424 | var obj = {}; 425 | obj.list1 = [obj]; 426 | obj.list2 = [obj]; 427 | obj.list3 = [{ prop: obj }]; 428 | 429 | refute.exception(function() { 430 | formatio.ascii(obj); 431 | }); 432 | }); 433 | 434 | it("formats symbol", function() { 435 | assert.equals(formatio.ascii(Symbol("value")), "Symbol(value)"); 436 | }); 437 | 438 | describe("sets", function() { 439 | it("formats sets", function() { 440 | var set = new Set(); 441 | 442 | set.add(2); 443 | set.add({ 444 | id: 42, 445 | prop: "Some" 446 | }); 447 | 448 | // eslint-disable-next-line quotes 449 | var expected = 'Set {2, { id: 42, prop: "Some" }}'; 450 | assert.equals(formatio.ascii(set), expected); 451 | }); 452 | 453 | it("limits the number of set members", function() { 454 | var fmt = formatio.configure({ limitChildrenCount: 30 }); 455 | var set = new Set(); 456 | 457 | for (var i = 0; i < 300; i++) { 458 | set.add(i); 459 | } 460 | 461 | var str = fmt.ascii(set); 462 | 463 | refute.contains(str, "30"); 464 | assert.contains(str, "29"); 465 | assert.contains(str, "[... 270 more elements]"); 466 | }); 467 | }); 468 | 469 | describe("maps", function() { 470 | it("formats maps", function() { 471 | var map = new Map(); 472 | 473 | map.set(42, "foo"); 474 | map.set("sinon", "bar"); 475 | map.set({ foo: "bar" }, "baz"); 476 | 477 | assert.equals( 478 | formatio.ascii(map), 479 | // eslint-disable-next-line quotes 480 | 'Map [[42, "foo"], ["sinon", "bar"], [{ foo: "bar" }, "baz"]]' 481 | ); 482 | }); 483 | 484 | it("limits the number of map members", function() { 485 | var fmt = formatio.configure({ limitChildrenCount: 30 }); 486 | var map = new Map(); 487 | 488 | for (var i = 0; i < 300; i++) { 489 | map.set(i, "some value"); 490 | } 491 | 492 | var str = fmt.ascii(map); 493 | 494 | refute.contains(str, "30"); 495 | assert.contains(str, "29"); 496 | assert.contains(str, "[... 270 more elements]"); 497 | }); 498 | }); 499 | 500 | describe("unquoted strings", function() { 501 | beforeEach(function() { 502 | this.formatter = formatio.configure({ quoteStrings: false }); 503 | }); 504 | 505 | it("does not quote strings", function() { 506 | assert.equals(this.formatter.ascii("Hey there"), "Hey there"); 507 | }); 508 | 509 | it("quotes string properties", function() { 510 | var obj = { hey: "Mister" }; 511 | // eslint-disable-next-line quotes 512 | assert.equals(this.formatter.ascii(obj), '{ hey: "Mister" }'); 513 | }); 514 | }); 515 | 516 | describe("numbers", function() { 517 | it("formats object with 0", function() { 518 | var str = formatio.ascii({ me: 0 }); 519 | refute.match(str, "-0"); 520 | }); 521 | 522 | it("formats object with -0", function() { 523 | var str = formatio.ascii({ me: -0 }); 524 | assert.match(str, "-0"); 525 | }); 526 | }); 527 | 528 | describe("DOM elements", function() { 529 | it("formats dom element", function() { 530 | var element = document.createElement("div"); 531 | 532 | assert.equals(formatio.ascii(element), "
"); 533 | }); 534 | 535 | it("formats dom element with attributes", function() { 536 | var element = document.createElement("div"); 537 | element.className = "hey there"; 538 | element.id = "ohyeah"; 539 | var str = formatio.ascii(element); 540 | 541 | assert.match(str, /
<\/div>/); 542 | assert.match(str, /class="hey there"/); 543 | assert.match(str, /id="ohyeah"/); 544 | }); 545 | 546 | it("formats dom element with content", function() { 547 | var element = document.createElement("div"); 548 | element.innerHTML = "Oh hi!"; 549 | 550 | assert.equals(formatio.ascii(element), "
Oh hi!
"); 551 | }); 552 | 553 | it("truncates dom element content", function() { 554 | var element = document.createElement("div"); 555 | element.innerHTML = 556 | "Oh hi! I'm Christian, and this is a lot of content"; 557 | 558 | assert.equals( 559 | formatio.ascii(element), 560 | "
Oh hi! I'm Christian[...]
" 561 | ); 562 | }); 563 | 564 | it("includes attributes and truncated content", function() { 565 | var element = document.createElement("div"); 566 | element.id = "anid"; 567 | element.lang = "en"; 568 | element.innerHTML = 569 | "Oh hi! I'm Christian, and this is a lot of content"; 570 | var str = formatio.ascii(element); 571 | 572 | assert.match( 573 | str, 574 | /
Oh hi! I'm Christian\[\.\.\.\]<\/div>/ 575 | ); 576 | assert.match(str, /lang="en"/); 577 | assert.match(str, /id="anid"/); 578 | }); 579 | 580 | it("strips out if attribute contenteditable is set to inherit", function() { 581 | var element = document.createElement("div"); 582 | element.setAttribute("contenteditable", "inherit"); 583 | assert.equals(formatio.ascii(element), "
"); 584 | }); 585 | 586 | it("strips out attribute that have no value", function() { 587 | var element = document.createElement("div"); 588 | element.setAttribute("id", ""); 589 | assert.equals(formatio.ascii(element), "
"); 590 | }); 591 | 592 | it("formats document object as toString", function() { 593 | var str; 594 | refute.exception(function() { 595 | str = formatio.ascii(document); 596 | }); 597 | 598 | assert.equals(str, "[object HTMLDocument]"); 599 | }); 600 | 601 | it("formats window object as toString", function() { 602 | var str; 603 | refute.exception(function() { 604 | str = formatio.ascii(window); 605 | }); 606 | 607 | assert.equals(str, "[object Window]"); 608 | }); 609 | }); 610 | 611 | describe("global object", function() { 612 | if (typeof global === "undefined") { 613 | return; 614 | } 615 | 616 | it("formats global object as toString", function() { 617 | var str; 618 | refute.exception(function() { 619 | str = formatio.ascii(global); 620 | }); 621 | 622 | assert.equals(str, "[object global]"); 623 | }); 624 | }); 625 | 626 | describe("BigInt", function() { 627 | before(function() { 628 | if (typeof BigInt === "undefined") { 629 | this.skip(); 630 | } 631 | }); 632 | 633 | // Note: We cannot use 0n, 1n, -1n here because not all 634 | // browsers and node versions support BigInt at the moment 635 | // and this will result in a SyntaxError 636 | 637 | it("formats 0n", function() { 638 | // eslint-disable-next-line 639 | assert.equals(formatio.ascii(BigInt("0")), "0"); 640 | }); 641 | 642 | it("formats positive values", function() { 643 | // eslint-disable-next-line 644 | assert.equals(formatio.ascii(BigInt("1")), "1"); 645 | }); 646 | 647 | it("formats negative values", function() { 648 | // eslint-disable-next-line 649 | assert.equals(formatio.ascii(BigInt("-1")), "-1"); 650 | }); 651 | }); 652 | 653 | describe("arguments", function() { 654 | it("excludes the values method", function() { 655 | var str = formatio.ascii(arguments); 656 | 657 | assert.equals(str, "{ }"); 658 | }); 659 | }); 660 | }); 661 | 662 | describe("formatio.constructorName", function() { 663 | before(function() { 664 | jsdom(); 665 | delete require.cache[require.resolve("./formatio")]; 666 | formatio = require("./formatio"); 667 | }); 668 | 669 | it("should return the constructors name", function() { 670 | assert.equals(formatio.constructorName([]), "Array"); 671 | }); 672 | }); 673 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: formatio 2 | theme: readthedocs 3 | repo_url: https://github.com/sinonjs/formatio/ 4 | 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@sinonjs/formatio", 3 | "version": "6.0.0", 4 | "description": "Human-readable object formatting", 5 | "homepage": "https://sinonjs.github.io/formatio/", 6 | "author": "Christian Johansen", 7 | "license": "BSD-3-Clause", 8 | "main": "./lib/formatio", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/sinonjs/formatio.git" 12 | }, 13 | "files": [ 14 | "lib/**/*[^test].js" 15 | ], 16 | "scripts": { 17 | "build": "npm run build:dist-folder && npm run build:bundle", 18 | "build:bundle": "rollup -c > dist/formatio.js", 19 | "build:dist-folder": "mkdirp dist", 20 | "lint": "eslint .", 21 | "prepublishOnly": "npm run build && mkdocs gh-deploy -r upstream || mkdocs gh-deploy -r origin", 22 | "test": "mocha 'lib/**/*.test.js'", 23 | "test-check-coverage": "npm run test-coverage && nyc check-coverage --branches 100 --functions 100 --lines 100", 24 | "test-coverage": "nyc --reporter text --reporter html --reporter lcovonly npm run test", 25 | "preversion": "npm run test-check-coverage", 26 | "version": "changes --commits --footer", 27 | "postversion": "git push --follow-tags && npm publish --access public" 28 | }, 29 | "dependencies": { 30 | "@sinonjs/commons": "^1", 31 | "@sinonjs/samsam": "^5.0.2" 32 | }, 33 | "devDependencies": { 34 | "@sinonjs/referee": "^5.0.0", 35 | "@studio/changes": "^2.0.0", 36 | "eslint": "^6.5.1", 37 | "eslint-config-prettier": "^6.4.0", 38 | "eslint-config-sinon": "^3.0.1", 39 | "eslint-plugin-ie11": "^1.0.0", 40 | "eslint-plugin-mocha": "^6.1.1", 41 | "eslint-plugin-prettier": "^3.1.1", 42 | "jsdom": "^16.1.0", 43 | "jsdom-global": "^3.0.2", 44 | "mocha": "^7.0.1", 45 | "nyc": "^15.0.0", 46 | "prettier": "^1.18.2", 47 | "rollup": "1.32.0", 48 | "rollup-plugin-commonjs": "10.1.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var commonjs = require("rollup-plugin-commonjs"); 4 | 5 | module.exports = { 6 | input: "lib/formatio.js", 7 | plugins: [commonjs({ sourceMap: false })], 8 | output: { 9 | exports: "named", 10 | format: "umd", 11 | globals: { 12 | // map '@sinonjs/samsam' to 'samsam' global variable 13 | "@sinonjs/samsam": "samsam" 14 | }, 15 | name: "formatio" 16 | }, 17 | external: ["@sinonjs/samsam"] 18 | }; 19 | --------------------------------------------------------------------------------