• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mac-s-g / react-json-view / #1956

16 Oct 2021 03:44PM UTC coverage: 76.364% (-7.0%) from 83.316%
#1956

push

dutzi
autofocus

290 of 397 branches covered (73.05%)

Branch coverage included in aggregate %.

2 of 5 new or added lines in 1 file covered. (40.0%)

89 existing lines in 5 files now uncovered.

508 of 648 relevant lines covered (78.4%)

41.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.6
/src/js/components/VariableEditor.js
1
import React from 'react';
2
import AutosizeTextarea from 'react-textarea-autosize';
3

4
import { handleObjectKeyKeyDown, toType } from './../helpers/util';
5
import dispatcher from './../helpers/dispatcher';
6
import parseInput from './../helpers/parseInput';
7
import stringifyVariable from './../helpers/stringifyVariable';
8
import CopyToClipboard from './CopyToClipboard';
9

10
//data type components
11
import {
12
    JsonBoolean,
13
    JsonDate,
14
    JsonFloat,
15
    JsonFunction,
16
    JsonInteger,
17
    JsonNan,
18
    JsonNull,
19
    JsonRegexp,
20
    JsonString,
21
    JsonUndefined
22
} from './DataTypes/DataTypes';
23

24
//clibboard icon
25
import { Edit, CheckCircle, RemoveCircle as Remove } from './icons';
26

27
//theme
28
import Theme from './../themes/getStyle';
29

30
class VariableEditor extends React.PureComponent {
31
    constructor(props) {
32
        super(props);
85✔
33
        this.state = {
85✔
34
            editMode: false,
35
            editValue: '',
36
            hovered: false,
37
            renameKey: false,
38
            parsedInput: {
39
                type: false,
40
                value: null
41
            }
42
        };
43
    }
44

45
    render() {
46
        const {
47
            variable,
48
            singleIndent,
49
            type,
50
            theme,
51
            namespace,
52
            indentWidth,
53
            enableClipboard,
54
            onEdit,
55
            onDelete,
56
            onSelect,
57
            displayArrayKey,
58
            quotesOnKeys
59
        } = this.props;
120✔
60
        const { editMode } = this.state;
120✔
61
        return (
120✔
62
            <div
63
                {...Theme(theme, 'objectKeyVal', {
64
                    paddingLeft: indentWidth * singleIndent
65
                })}
66
                onMouseEnter={() =>
67
                    this.setState({ ...this.state, hovered: true })
×
68
                }
69
                onMouseLeave={() =>
70
                    this.setState({ ...this.state, hovered: false })
×
71
                }
72
                class="variable-row"
73
                key={variable.name}
74
            >
75
                {type == 'array' ? (
120✔
76
                    displayArrayKey ? (
26✔
77
                        <span
78
                            {...Theme(theme, 'array-key')}
79
                            key={variable.name + '_' + namespace}
80
                        >
81
                            <span
82
                                tabIndex={0}
83
                                onKeyDown={e =>
UNCOV
84
                                    handleObjectKeyKeyDown('array-variable', e)
×
85
                                }
86
                            >
87
                                {variable.name}
88
                            </span>
89
                            <div {...Theme(theme, 'colon')}>:</div>
90
                        </span>
91
                    ) : null
92
                ) : (
93
                    <span>
94
                        <span
95
                            {...Theme(theme, 'object-name')}
96
                            class="object-key variable-name"
97
                            tabIndex={0}
98
                            onKeyDown={e =>
UNCOV
99
                                handleObjectKeyKeyDown('variable', e)
×
100
                            }
101
                            key={variable.name + '_' + namespace}
102
                        >
103
                            {!!quotesOnKeys && (
145✔
104
                                <span style={{ verticalAlign: 'top' }}>"</span>
105
                            )}
106
                            <span style={{ display: 'inline-block' }}>
107
                                {variable.name}
108
                            </span>
109
                            {!!quotesOnKeys && (
145✔
110
                                <span style={{ verticalAlign: 'top' }}>"</span>
111
                            )}
112
                        </span>
113
                        <span {...Theme(theme, 'colon')}>:</span>
114
                    </span>
115
                )}
116
                <div
117
                    class="variable-value"
118
                    onClick={
119
                        onSelect === false && onEdit === false
299✔
120
                            ? null
121
                            : e => {
UNCOV
122
                                  let location = [...namespace];
×
UNCOV
123
                                  if (
×
124
                                      (e.ctrlKey || e.metaKey) &&
×
125
                                      onEdit !== false
126
                                  ) {
UNCOV
127
                                      this.prepopInput(variable);
×
UNCOV
128
                                  } else if (onSelect !== false) {
×
UNCOV
129
                                      location.shift();
×
UNCOV
130
                                      onSelect({
×
131
                                          ...variable,
132
                                          namespace: location
133
                                      });
134
                                  }
135
                              }
136
                    }
137
                    {...Theme(theme, 'variableValue', {
138
                        cursor: onSelect === false ? 'default' : 'pointer'
120✔
139
                    })}
140
                >
141
                    {this.getValue(variable, editMode)}
142
                </div>
143
                {enableClipboard ? (
120✔
144
                    <CopyToClipboard
145
                        rowHovered={this.state.hovered}
146
                        hidden={editMode}
147
                        src={variable.value}
148
                        clickCallback={enableClipboard}
149
                        {...{ theme, namespace: [...namespace, variable.name] }}
150
                    />
151
                ) : null}
152
                {onEdit !== false && editMode == false
300✔
153
                    ? this.getEditIcon()
154
                    : null}
155
                {onDelete !== false && editMode == false
301✔
156
                    ? this.getRemoveIcon()
157
                    : null}
158
            </div>
159
        );
160
    }
161

162
    getEditIcon = () => {
85✔
163
        const { variable, theme } = this.props;
44✔
164

165
        return (
44✔
166
            <div
167
                class="click-to-edit"
168
                style={{
169
                    verticalAlign: 'top',
170
                    display: this.state.hovered ? 'inline-block' : 'none'
44!
171
                }}
172
            >
173
                <Edit
174
                    class="click-to-edit-icon"
175
                    {...Theme(theme, 'editVarIcon')}
176
                    onClick={() => {
177
                        this.prepopInput(variable);
13✔
178
                    }}
179
                />
180
            </div>
181
        );
182
    };
183

184
    prepopInput = variable => {
85✔
185
        if (this.props.onEdit !== false) {
13!
186
            const stringifiedValue = stringifyVariable(variable.value);
13✔
187
            const detected = parseInput(stringifiedValue);
13✔
188
            this.setState({
13✔
189
                editMode: true,
190
                editValue: stringifiedValue,
191
                parsedInput: {
192
                    type: detected.type,
193
                    value: detected.value
194
                }
195
            });
196
        }
197
    };
198

199
    getRemoveIcon = () => {
85✔
200
        const { variable, namespace, theme, rjvId } = this.props;
45✔
201

202
        return (
45✔
203
            <div
204
                class="click-to-remove"
205
                style={{
206
                    verticalAlign: 'top',
207
                    display: this.state.hovered ? 'inline-block' : 'none'
45!
208
                }}
209
            >
210
                <Remove
211
                    class="click-to-remove-icon"
212
                    {...Theme(theme, 'removeVarIcon')}
213
                    onClick={() => {
UNCOV
214
                        dispatcher.dispatch({
×
215
                            name: 'VARIABLE_REMOVED',
216
                            rjvId: rjvId,
217
                            data: {
218
                                name: variable.name,
219
                                namespace: namespace,
220
                                existing_value: variable.value,
221
                                variable_removed: true
222
                            }
223
                        });
224
                    }}
225
                />
226
            </div>
227
        );
228
    };
229

230
    getValue = (variable, editMode) => {
85✔
231
        const type = editMode ? false : variable.type;
120✔
232
        const { props } = this;
120✔
233
        switch (type) {
120!
234
            case false:
235
                return this.getEditInput();
16✔
236
            case 'string':
237
                return <JsonString value={variable.value} {...props} />;
41✔
238
            case 'integer':
239
                return <JsonInteger value={variable.value} {...props} />;
24✔
240
            case 'float':
241
                return <JsonFloat value={variable.value} {...props} />;
2✔
242
            case 'boolean':
243
                return <JsonBoolean value={variable.value} {...props} />;
9✔
244
            case 'function':
245
                return <JsonFunction value={variable.value} {...props} />;
5✔
246
            case 'null':
247
                return <JsonNull {...props} />;
5✔
248
            case 'nan':
249
                return <JsonNan {...props} />;
5✔
250
            case 'undefined':
251
                return <JsonUndefined {...props} />;
2✔
252
            case 'date':
UNCOV
253
                return <JsonDate value={variable.value} {...props} />;
×
254
            case 'regexp':
255
                return <JsonRegexp value={variable.value} {...props} />;
4✔
256
            default:
257
                // catch-all for types that weren't anticipated
258
                return (
7✔
259
                    <div class="object-value">
260
                        {JSON.stringify(variable.value)}
261
                    </div>
262
                );
263
        }
264
    };
265

266
    getEditInput = () => {
85✔
267
        const { theme } = this.props;
16✔
268
        const { editValue } = this.state;
16✔
269

270
        return (
16✔
271
            <div>
272
                <AutosizeTextarea
273
                    type="text"
UNCOV
274
                    inputRef={input => input && input.focus()}
×
275
                    value={editValue}
276
                    class="variable-editor"
277
                    onChange={event => {
278
                        const value = event.target.value;
×
UNCOV
279
                        const detected = parseInput(value);
×
280
                        this.setState({
×
281
                            editValue: value,
282
                            parsedInput: {
283
                                type: detected.type,
284
                                value: detected.value
285
                            }
286
                        });
287
                    }}
288
                    onKeyDown={e => {
UNCOV
289
                        switch (e.key) {
×
290
                            case 'Escape': {
UNCOV
291
                                this.setState({
×
292
                                    editMode: false,
293
                                    editValue: ''
294
                                });
UNCOV
295
                                break;
×
296
                            }
297
                            case 'Enter': {
UNCOV
298
                                if (e.ctrlKey || e.metaKey) {
×
UNCOV
299
                                    this.submitEdit(true);
×
300
                                }
UNCOV
301
                                break;
×
302
                            }
303
                        }
UNCOV
304
                        e.stopPropagation();
×
305
                    }}
306
                    placeholder="update this value"
307
                    minRows={2}
308
                    {...Theme(theme, 'edit-input')}
309
                />
310
                <div {...Theme(theme, 'edit-icon-container')}>
311
                    <Remove
312
                        class="edit-cancel"
313
                        {...Theme(theme, 'cancel-icon')}
314
                        onClick={() => {
315
                            this.setState({ editMode: false, editValue: '' });
2✔
316
                        }}
317
                    />
318
                    <CheckCircle
319
                        class="edit-check string-value"
320
                        {...Theme(theme, 'check-icon')}
321
                        onClick={() => {
322
                            this.submitEdit();
1✔
323
                        }}
324
                    />
325
                    <div>{this.showDetected()}</div>
326
                </div>
327
            </div>
328
        );
329
    };
330

331
    submitEdit = submit_detected => {
85✔
332
        const { variable, namespace, rjvId } = this.props;
1✔
333
        const { editValue, parsedInput } = this.state;
1✔
334
        let new_value = editValue;
1✔
335
        if (submit_detected && parsedInput.type) {
1!
UNCOV
336
            new_value = parsedInput.value;
×
337
        }
338
        this.setState({
1✔
339
            editMode: false
340
        });
341
        dispatcher.dispatch({
1✔
342
            name: 'VARIABLE_UPDATED',
343
            rjvId: rjvId,
344
            data: {
345
                name: variable.name,
346
                namespace: namespace,
347
                existing_value: variable.value,
348
                new_value: new_value,
349
                variable_removed: false
350
            }
351
        });
352
    };
353

354
    showDetected = () => {
85✔
355
        const { theme, variable, namespace, rjvId } = this.props;
16✔
356
        const { type, value } = this.state.parsedInput;
16✔
357
        const detected = this.getDetectedInput();
16✔
358
        if (detected) {
16✔
359
            return (
8✔
360
                <div>
361
                    <div {...Theme(theme, 'detected-row')}>
362
                        {detected}
363
                        <CheckCircle
364
                            class="edit-check detected"
365
                            style={{
366
                                verticalAlign: 'top',
367
                                paddingLeft: '3px',
368
                                ...Theme(theme, 'check-icon').style
369
                            }}
370
                            onClick={() => {
UNCOV
371
                                this.submitEdit(true);
×
372
                            }}
373
                        />
374
                    </div>
375
                </div>
376
            );
377
        }
378
    };
379

380
    getDetectedInput = () => {
85✔
381
        const { parsedInput } = this.state;
16✔
382
        const { type, value } = parsedInput;
16✔
383
        const { props } = this;
16✔
384
        const { theme } = props;
16✔
385

386
        if (type !== false) {
16✔
387
            switch (type.toLowerCase()) {
8!
388
                case 'object':
389
                    return (
1✔
390
                        <span>
391
                            <span
392
                                style={{
393
                                    ...Theme(theme, 'brace').style,
394
                                    cursor: 'default'
395
                                }}
396
                            >
397
                                {'{'}
398
                            </span>
399
                            <span
400
                                style={{
401
                                    ...Theme(theme, 'ellipsis').style,
402
                                    cursor: 'default'
403
                                }}
404
                            >
405
                                ...
406
                            </span>
407
                            <span
408
                                style={{
409
                                    ...Theme(theme, 'brace').style,
410
                                    cursor: 'default'
411
                                }}
412
                            >
413
                                {'}'}
414
                            </span>
415
                        </span>
416
                    );
417
                case 'array':
418
                    return (
1✔
419
                        <span>
420
                            <span
421
                                style={{
422
                                    ...Theme(theme, 'brace').style,
423
                                    cursor: 'default'
424
                                }}
425
                            >
426
                                {'['}
427
                            </span>
428
                            <span
429
                                style={{
430
                                    ...Theme(theme, 'ellipsis').style,
431
                                    cursor: 'default'
432
                                }}
433
                            >
434
                                ...
435
                            </span>
436
                            <span
437
                                style={{
438
                                    ...Theme(theme, 'brace').style,
439
                                    cursor: 'default'
440
                                }}
441
                            >
442
                                {']'}
443
                            </span>
444
                        </span>
445
                    );
446
                case 'string':
UNCOV
447
                    return <JsonString value={value} {...props} />;
×
448
                case 'integer':
449
                    return <JsonInteger value={value} {...props} />;
2✔
450
                case 'float':
451
                    return <JsonFloat value={value} {...props} />;
1✔
452
                case 'boolean':
UNCOV
453
                    return <JsonBoolean value={value} {...props} />;
×
454
                case 'function':
UNCOV
455
                    return <JsonFunction value={value} {...props} />;
×
456
                case 'null':
457
                    return <JsonNull {...props} />;
1✔
458
                case 'nan':
459
                    return <JsonNan {...props} />;
1✔
460
                case 'undefined':
461
                    return <JsonUndefined {...props} />;
1✔
462
                case 'date':
UNCOV
463
                    return <JsonDate value={new Date(value)} {...props} />;
×
464
            }
465
        }
466
    };
467
}
468

469
//export component
470
export default VariableEditor;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc