1 | 'use strict';
|
2 |
|
3 | const _ = require('lodash');
|
4 | const Bluebird = require('bluebird');
|
5 |
|
6 | function asSelfDescribing(value) {
|
7 | if (!value || !_.isFunction(value.describeTo)) {
|
8 | return {
|
9 | describeTo(description) {
|
10 | description.appendValue(value);
|
11 | }
|
12 | };
|
13 | } else {
|
14 | return value;
|
15 | }
|
16 | }
|
17 |
|
18 | function makeNaNReadable(__key, value) {
|
19 | return typeof value === 'number' && isNaN(value) ? '<NaN>' : value;
|
20 | }
|
21 |
|
22 | function Description() {
|
23 | let value = '';
|
24 | return {
|
25 | useJsonForObjects: true,
|
26 | indentation: 0,
|
27 | append(text) {
|
28 | if (this.indentation) {
|
29 | text = ('' + text).replace('\n', _.padEnd('\n', this.indentation + 1, '\t'));
|
30 | }
|
31 | try {
|
32 | value += text;
|
33 | } catch (e) {
|
34 | value += '[ ' + typeof text + ']';
|
35 | }
|
36 | return this;
|
37 | },
|
38 | indented(describingfn) {
|
39 | this.indentation += 1;
|
40 | const result = describingfn();
|
41 | if (result && _.isFunction(result.then)) {
|
42 | return Bluebird.resolve(result)
|
43 | .finally(() => {
|
44 | this.indentation -= 1;
|
45 | });
|
46 | } else {
|
47 | this.indentation -= 1;
|
48 | return result;
|
49 | }
|
50 | },
|
51 | appendDescriptionOf(selfDescribing) {
|
52 | if (selfDescribing && _.isFunction(selfDescribing.describeTo)) {
|
53 | selfDescribing.describeTo(this);
|
54 | } else {
|
55 | this.appendValue(selfDescribing);
|
56 | }
|
57 | return this;
|
58 | },
|
59 | appendValue(value, indentLists) {
|
60 | if (_.isUndefined(value)) {
|
61 | this.append('undefined');
|
62 | } else if (_.isNull(value)) {
|
63 | this.append('null');
|
64 | } else if (_.isString(value)) {
|
65 | this.append('"');
|
66 | this.append(value);
|
67 | this.append('"');
|
68 | } else if (_.isNumber(value)) {
|
69 | this.append('<');
|
70 | this.append(value);
|
71 | this.append('>');
|
72 | } else if (_.isArray(value)) {
|
73 | if (indentLists && value.length > 1) {
|
74 | this.indented(() => this.appendList('[\n', ',\n', '', value))
|
75 | .append('\n]');
|
76 | } else {
|
77 | this.appendList('[', ', ', ']', value);
|
78 | }
|
79 | } else if (isDomNode(value)) {
|
80 | this.append('DOM node ')
|
81 | .appendValue(_.isFunction(value.html) ? value.html() : value.outerHTML);
|
82 | } else if (_.isFunction(value)) {
|
83 | this.append('Function' + (value.name ? ' ' + value.name : ''));
|
84 | } else if (_.isRegExp(value)) {
|
85 | this.append(value.toString());
|
86 | } else if (this.useJsonForObjects) {
|
87 | try {
|
88 | this.append(JSON.stringify(value, makeNaNReadable));
|
89 | } catch (e) {
|
90 | const oldJsonFlag = this.useJsonForObjects;
|
91 | this.useJsonForObjects = false;
|
92 | this.appendNonJson(value);
|
93 | this.useJsonForObjects = oldJsonFlag;
|
94 | }
|
95 | } else {
|
96 | this.append(value);
|
97 | }
|
98 | return this;
|
99 | },
|
100 | appendNonJson(value) {
|
101 | this.append('{');
|
102 | let first = true;
|
103 | _.forEach(value, (innerValue, key) => {
|
104 | if (!first) {
|
105 | this.append(', ');
|
106 | }
|
107 | first = false;
|
108 | this.append(key)
|
109 | .append(': ');
|
110 | this.appendValue(innerValue);
|
111 | }, this);
|
112 | this.append('}');
|
113 | },
|
114 | appendList(start, separator, end, list) {
|
115 | this.append(start);
|
116 | _.forEach(list, (value, index) => {
|
117 | if (index !== 0) {
|
118 | this.append(separator);
|
119 | }
|
120 | this.appendDescriptionOf(asSelfDescribing(value));
|
121 | }, this);
|
122 | this.append(end);
|
123 | return this;
|
124 | },
|
125 | get() {
|
126 | return value;
|
127 | }
|
128 | };
|
129 |
|
130 | function isDomNode(value) {
|
131 | if (!value) {
|
132 | return false;
|
133 | }
|
134 | return _.isFunction(value.appendChild) && _.isFunction(value.isEqualNode) && !_.isUndefined(value.outerHTML) ||
|
135 | _.isFunction(value.html) && _.isFunction(value.text);
|
136 | }
|
137 | }
|
138 |
|
139 | module.exports = Description;
|