Skip to content

Commit

Permalink
Merge pull request #6 from emartech/json-badge-feature
Browse files Browse the repository at this point in the history
improvements for displaying JSON contents

Ticket: SUITEDEV-25985
  • Loading branch information
hawser86 authored Jan 29, 2021
2 parents 8bceeb4 + ee6b93a commit 59c7467
Show file tree
Hide file tree
Showing 9 changed files with 291 additions and 26 deletions.
13 changes: 13 additions & 0 deletions src/renderer/components/secret-editor/json-badge/json-badge.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<e-tooltip
:content="tooltipText"
:disabled="!tooltipText"
>
<div
:id="id"
:class="`e-label e-label-${type}`"
:style="style"
@click="$emit('click')"
>
{{ content }}
</div>
</e-tooltip>
17 changes: 17 additions & 0 deletions src/renderer/components/secret-editor/json-badge/json-badge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default {
name: 'json-badge',
template: require('./json-badge.html'),
props: {
id: { type: String, default: '' },
type: { type: String, default: 'info' },
content: { type: String, default: 'JSON' },
tooltipText: { type: String | null, default: null }
},
computed: {
style() {
return `position: absolute; top: 7px; right: 0; cursor: ${
this.type === 'info' ? 'pointer' : 'default'
}`;
}
}
};
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
<div style="position: relative; padding-right: 70px;">
<div style="display: flex; position: relative; padding-right: 70px">
<textarea
class="e-input e-input-textarea e-input-auto_height"
rows="1"
style="white-space: pre; resize: vertical;"
style="white-space: pre; resize: vertical"
:value="value"
@input="changeSecretValue"
wrap="soft"
ref="textarea"
/>
<div class="e-label e-label-info" style="position: absolute; top: 7px; right: 0"
v-if="isValidJson">
JSON
</div>
<json-badge
v-if="isValidJson || isJsonWithErrors"
id="badge"
:type="isJsonWithErrors ? 'danger' : 'info'"
@click="changeJsonState($event)"
:tooltip-text="isJsonWithErrors ? getJsonParseErrorMessage : ''"
/>
</div>
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import isValidJson from '../../../lib/is-valid-json/is-valid-json';
import {
isValidJson,
isJsonWithErrors,
getParseErrorMessage,
minify,
prettify,
isJsonMinified
} from '../../../lib/json-helper/json-helper';
import JsonBadge from '../json-badge/json-badge';

export default {
name: 'secret-editor-textarea',
template: require('./secret-editor-textarea.html'),
components: {
JsonBadge
},
props: {
value: { type: String, required: true, default: '' }
},
computed: {
isValidJson() {
return isValidJson(this.value);
},
isJsonWithErrors() {
return isJsonWithErrors(this.value);
},
getJsonParseErrorMessage() {
return getParseErrorMessage(this.value);
}
},
methods: {
Expand All @@ -20,6 +37,15 @@ export default {
await this.$nextTick();
this.$refs.textarea.style.height = 'auto';
this.$refs.textarea.style.height = `${this.$refs.textarea.scrollHeight}px`;
},
changeJsonState() {
if (!this.isValidJson) return;

if (isJsonMinified(this.value)) {
this.$emit('change', prettify(this.value));
} else {
this.$emit('change', minify(this.value));
}
}
},
watch: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { mount } from '@vue/test-utils';
import SecretEditorTextarea from './secret-editor-textarea';

describe('SecretEditorTextarea', () => {
describe('#changeJsonState', () => {
it('should change JSON formatting from prettified to minified', () => {
const wrapper = mount(SecretEditorTextarea, {
propsData: {
value: `[{\n
"key": "value"
\n}]`
}
});

wrapper.find('#badge').trigger('click');

const lines = _getNumberOfJsonStringLinesAfterSplittingByNewLine(
wrapper.emitted().change[0][0]
);
expect(lines).to.eql(1);
});

it('should change JSON formatting from minified to prettified', () => {
const wrapper = mount(SecretEditorTextarea, {
propsData: { value: '[{ "key": "value" }]' }
});

wrapper.find('#badge').trigger('click');

const lines = _getNumberOfJsonStringLinesAfterSplittingByNewLine(
wrapper.emitted().change[0][0]
);
expect(lines).to.greaterThan(1);
});

it('should not change JSON formatting when value is not a valid JSON', () => {
const value = '{ "key": "value", "key-without-value" }';
const wrapper = mount(SecretEditorTextarea, {
propsData: { value }
});

wrapper.find('#badge').trigger('click');

expect(wrapper.emitted()).to.eql({});
});
});

describe('#getJsonParseErrorMessage', () => {
it('should return an error message when json string is invalid', () => {
const invalidJsonString = '[{ "inv: alid }]';
const { vm } = mount(SecretEditorTextarea, {
propsData: { value: invalidJsonString }
});
expect(vm.getJsonParseErrorMessage).to.be.eql('Unexpected end of JSON input');
});

it('should return another error message when json string is invalid differently', () => {
const invalidJsonString = '[{ inv: "alid" }]';
const { vm } = mount(SecretEditorTextarea, {
propsData: { value: invalidJsonString }
});
expect(vm.getJsonParseErrorMessage).to.be.eql('Unexpected token i in JSON at position 3');
});

it('should not return anything when json string is valid', () => {
const validJsonString = '[{ "val": "id" }]';
const { vm } = mount(SecretEditorTextarea, {
propsData: { value: validJsonString }
});
expect(vm.getJsonParseErrorMessage).to.be.undefined;
});
});

describe('#isJsonWithErrors', () => {
it('should return true when input seems like a json but it contains errors', () => {
const invalidJsonString = '[{ "val: "id }]';
const { vm } = mount(SecretEditorTextarea, {
propsData: { value: invalidJsonString }
});
expect(vm.isJsonWithErrors).to.be.true;
});

it('should return true when input starts like a json but it is not a valid one', () => {
const invalidJsonString = '[{ "not_json" }]';
const { vm } = mount(SecretEditorTextarea, {
propsData: { value: invalidJsonString }
});
expect(vm.isJsonWithErrors).to.be.true;
});

it('should return false when input not starts like a valid json', () => {
const invalidJsonString = '] "val": "id" ]';
const { vm } = mount(SecretEditorTextarea, {
propsData: { value: invalidJsonString }
});
expect(vm.isJsonWithErrors).to.be.false;
});
});
});

const _getNumberOfJsonStringLinesAfterSplittingByNewLine = (jsonString) => {
return jsonString.split('\n').length;
};
8 changes: 0 additions & 8 deletions src/renderer/lib/is-valid-json/is-valid-json.js

This file was deleted.

11 changes: 0 additions & 11 deletions src/renderer/lib/is-valid-json/is-valid-json.spec.js

This file was deleted.

45 changes: 45 additions & 0 deletions src/renderer/lib/json-helper/json-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
export const isValidJson = (data) => {
if (!_looksLikeJson(data)) return false;
try {
JSON.parse(data);
return true;
} catch (error) {
return false;
}
};

export const isJsonWithErrors = (value) => {
return _looksLikeJson(value) && !isValidJson(value);
};

const _looksLikeJson = (value) => {
const valueWithoutSpaces = value.toString().replace(/\s/g, '');
return (
(valueWithoutSpaces.startsWith('{') && valueWithoutSpaces.endsWith('}')) ||
(valueWithoutSpaces.startsWith('[') && valueWithoutSpaces.endsWith(']'))
);
};

export const minify = (json) => {
return _formatJson(json);
};

export const prettify = (json) => {
return _formatJson(json, 2);
};

const _formatJson = (json, spaces = 0) => {
return JSON.stringify(JSON.parse(json), null, spaces);
};

export const isJsonMinified = (jsonString) => {
return jsonString.split('\n').length === 1;
};

export const getParseErrorMessage = (value) => {
try {
JSON.parse(value);
} catch (error) {
return error.message;
}
};
77 changes: 77 additions & 0 deletions src/renderer/lib/json-helper/json-helper.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { isValidJson, isJsonWithErrors, minify, prettify } from './json-helper';

describe('#isValidJson', () => {
it('should return true when string is a valid JSON', () => {
expect(
isValidJson('{"addressee": "Luke", "message": "I am your father", "response": "Noooo!"}')
).to.be.true;
});

it('should return false when string is not a valid JSON', () => {
expect(isValidJson('Noooo!')).to.be.false;
});
it('should return false when input is a boolean value', () => {
expect(isValidJson(true)).to.be.false;
});
it('should return false when input is a boolean value as a string', () => {
expect(isValidJson('false')).to.be.false;
});
it('should return false when input is a number', () => {
expect(isValidJson(1)).to.be.false;
});
it('should return false when input is a number as string', () => {
expect(isValidJson('1')).to.be.false;
});
});

describe('#isJsonWithErrors', () => {
it('should return true when input seems like a json but it contains errors', () => {
const invalidJsonString = '[{ "val: "id }]';

const result = isJsonWithErrors(invalidJsonString);

expect(result).to.be.true;
});

it('should return true when input starts like a json but it is not a valid one', () => {
const invalidJsonString = '[{ "not_json" }]';

const result = isJsonWithErrors(invalidJsonString);

expect(result).to.be.true;
});

it('should return false when input not starts like a valid json', () => {
const invalidJsonString = '] "val": "id" ]';

const result = isJsonWithErrors(invalidJsonString);

expect(result).to.be.false;
});
});

describe('#minify and prettify', () => {
it('should minify a stringified json', () => {
const jsonString = `[{\n
"key": "value"
\n}]`;

const minifiedJsonString = minify(jsonString);

expect(_getNumberOfLines(jsonString)).to.eql(5);
expect(_getNumberOfLines(minifiedJsonString)).to.eql(1);
});

it('should prettify a minified json', () => {
const jsonString = '[{"key":"value"}]';

const prettifiedJsonString = prettify(jsonString);

expect(_getNumberOfLines(jsonString)).to.eql(1);
expect(_getNumberOfLines(prettifiedJsonString)).to.eql(5);
});
});

const _getNumberOfLines = (string) => {
return string.split('\n').length;
};

0 comments on commit 59c7467

Please sign in to comment.