UI/kv codemirror diff (#13000)
* staying with jsondiff * routing setup * send compare against data to component after using new adapater method to return the version data. * functionality * fix issue on route transition not calling model hook * formatting * update version * changelog * glimmerize the json-editor component * catch up * passing tracked property from child to parent * pull out of jsonEditor * fix issue with message * icon * fix some issues with right selection * changes and convert to component * integration test * tests * fixes * cleanup * cleanup 2 * fixes * fix test by spread attributes * remove log * remove
This commit is contained in:
parent
82d49a08fb
commit
43148ed9f2
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
ui: Add version diff view for KV V2
|
||||
```
|
|
@ -54,6 +54,16 @@ export default ApplicationAdapter.extend({
|
|||
});
|
||||
},
|
||||
|
||||
querySecretDataByVersion(id) {
|
||||
return this.ajax(this.urlForQueryRecord(id), 'GET')
|
||||
.then(resp => {
|
||||
return resp.data;
|
||||
})
|
||||
.catch(error => {
|
||||
return error.data;
|
||||
});
|
||||
},
|
||||
|
||||
urlForCreateRecord(modelName, snapshot) {
|
||||
let backend = snapshot.belongsTo('secret').belongsTo('engine').id;
|
||||
let path = snapshot.attr('path');
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/* eslint-disable no-undef */
|
||||
import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
|
||||
/**
|
||||
* @module DiffVersionSelector
|
||||
* DiffVersionSelector component includes a toolbar and diff view between KV 2 versions. It uses the library jsondiffpatch.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <DiffVersionSelector @model={model}/>
|
||||
* ```
|
||||
* @param {object} model - model that comes from secret-v2-version
|
||||
*/
|
||||
|
||||
export default class DiffVersionSelector extends Component {
|
||||
@tracked leftSideVersionDataSelected = null;
|
||||
@tracked leftSideVersionSelected = null;
|
||||
@tracked rightSideVersionDataSelected = null;
|
||||
@tracked rightSideVersionSelected = null;
|
||||
@tracked statesMatch = false;
|
||||
@tracked visualDiff = null;
|
||||
@service store;
|
||||
|
||||
adapter = this.store.adapterFor('secret-v2-version');
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.createVisualDiff();
|
||||
}
|
||||
|
||||
get leftSideDataInit() {
|
||||
let string = `["${this.args.model.engineId}", "${this.args.model.id}", "${this.args.model.currentVersion}"]`;
|
||||
return this.adapter
|
||||
.querySecretDataByVersion(string)
|
||||
.then(response => response.data)
|
||||
.catch(() => null);
|
||||
}
|
||||
get rightSideDataInit() {
|
||||
let string = `["${this.args.model.engineId}", "${this.args.model.id}", "${this.rightSideVersionInit}"]`;
|
||||
return this.adapter
|
||||
.querySecretDataByVersion(string)
|
||||
.then(response => response.data)
|
||||
.catch(() => null);
|
||||
}
|
||||
get rightSideVersionInit() {
|
||||
// initial value of right side version is one less than the current version
|
||||
return this.args.model.currentVersion === 1 ? 0 : this.args.model.currentVersion - 1;
|
||||
}
|
||||
|
||||
async createVisualDiff() {
|
||||
let diffpatcher = jsondiffpatch.create({});
|
||||
let leftSideVersionData = this.leftSideVersionDataSelected || (await this.leftSideDataInit);
|
||||
let rightSideVersionData = this.rightSideVersionDataSelected || (await this.rightSideDataInit);
|
||||
let delta = diffpatcher.diff(rightSideVersionData, leftSideVersionData);
|
||||
if (delta === undefined) {
|
||||
this.statesMatch = true;
|
||||
this.visualDiff = JSON.stringify(leftSideVersionData, undefined, 2); // params: value, replacer (all properties included), space (white space and indentation, line break, etc.)
|
||||
} else {
|
||||
this.statesMatch = false;
|
||||
this.visualDiff = jsondiffpatch.formatters.html.format(delta, rightSideVersionData);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
async selectVersion(selectedVersion, actions, side) {
|
||||
let string = `["${this.args.model.engineId}", "${this.args.model.id}", "${selectedVersion}"]`;
|
||||
let secretData = await this.adapter.querySecretDataByVersion(string);
|
||||
if (side === 'left') {
|
||||
this.leftSideVersionDataSelected = secretData.data;
|
||||
this.leftSideVersionSelected = selectedVersion;
|
||||
}
|
||||
if (side === 'right') {
|
||||
this.rightSideVersionDataSelected = secretData.data;
|
||||
this.rightSideVersionSelected = selectedVersion;
|
||||
}
|
||||
await this.createVisualDiff();
|
||||
// close dropdown menu.
|
||||
actions.close();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,21 @@
|
|||
import Component from '@ember/component';
|
||||
import Component from '@glimmer/component';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
/**
|
||||
* @module JsonEditor
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* <JsonEditor @title="Policy" @value={{codemirror.string}} @valueUpdated={{ action "codemirrorUpdate"}} />
|
||||
* ```
|
||||
*
|
||||
* @param {string} [title] - Name above codemirror view
|
||||
* @param {string} value - a specific string the comes from codemirror. It's the value inside the codemirror display
|
||||
* @param {Function} [valueUpdated] - action to preform when you edit the codemirror value.
|
||||
* @param {Function} [onFocusOut] - action to preform when you focus out of codemirror.
|
||||
* @param {string} [helpText] - helper text.
|
||||
* @param {object} [options] - option object that overrides codemirror default options such as the styling.
|
||||
*/
|
||||
|
||||
const JSON_EDITOR_DEFAULTS = {
|
||||
// IMPORTANT: `gutters` must come before `lint` since the presence of
|
||||
|
@ -13,20 +30,16 @@ const JSON_EDITOR_DEFAULTS = {
|
|||
showCursorWhenSelecting: true,
|
||||
};
|
||||
|
||||
export default Component.extend({
|
||||
showToolbar: true,
|
||||
title: null,
|
||||
subTitle: null,
|
||||
helpText: null,
|
||||
value: null,
|
||||
options: null,
|
||||
valueUpdated: null,
|
||||
onFocusOut: null,
|
||||
readOnly: false,
|
||||
export default class JsonEditorComponent extends Component {
|
||||
value = null;
|
||||
valueUpdated = null;
|
||||
onFocusOut = null;
|
||||
readOnly = false;
|
||||
options = null;
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.options = { ...JSON_EDITOR_DEFAULTS, ...this.options };
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.options = { ...JSON_EDITOR_DEFAULTS, ...this.args.options };
|
||||
if (this.options.autoHeight) {
|
||||
this.options.viewportMargin = Infinity;
|
||||
delete this.options.autoHeight;
|
||||
|
@ -36,18 +49,23 @@ export default Component.extend({
|
|||
this.options.lineNumbers = false;
|
||||
delete this.options.gutters;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
actions: {
|
||||
updateValue(...args) {
|
||||
if (this.valueUpdated) {
|
||||
this.valueUpdated(...args);
|
||||
}
|
||||
},
|
||||
onFocus(...args) {
|
||||
if (this.onFocusOut) {
|
||||
this.onFocusOut(...args);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
get getShowToolbar() {
|
||||
return this.args.showToolbar === false ? false : true;
|
||||
}
|
||||
|
||||
@action
|
||||
updateValue(...args) {
|
||||
if (this.args.valueUpdated) {
|
||||
this.args.valueUpdated(...args);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
onFocus(...args) {
|
||||
if (this.args.onFocusOut) {
|
||||
this.args.onFocusOut(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import Controller from '@ember/controller';
|
||||
import BackendCrumbMixin from 'vault/mixins/backend-crumb';
|
||||
|
||||
export default class DiffController extends Controller.extend(BackendCrumbMixin) {}
|
|
@ -103,6 +103,7 @@ Router.map(function() {
|
|||
|
||||
this.route('list', { path: '/list/*secret' });
|
||||
this.route('show', { path: '/show/*secret' });
|
||||
this.route('diff', { path: '/diff/*id' });
|
||||
this.route('metadata', { path: '/metadata/*secret' });
|
||||
this.route('edit-metadata', { path: '/edit-metadata/*secret' });
|
||||
this.route('create', { path: '/create/*secret' });
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class diff extends Route {
|
||||
@service store;
|
||||
|
||||
beforeModel() {
|
||||
let { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
||||
this.backend = backend;
|
||||
}
|
||||
|
||||
model(params) {
|
||||
let { id } = params;
|
||||
return this.store.queryRecord('secret-v2', {
|
||||
backend: this.backend,
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.set('backend', this.backend); // for backendCrumb
|
||||
controller.set('id', model.id); // for navigation on tabs
|
||||
controller.set('model', model);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
.visual-diff {
|
||||
background-color: black;
|
||||
|
||||
pre {
|
||||
color: $ui-gray-010;
|
||||
}
|
||||
}
|
||||
|
||||
.jsondiffpatch-deleted .jsondiffpatch-property-name,
|
||||
.jsondiffpatch-deleted pre,
|
||||
.jsondiffpatch-modified .jsondiffpatch-left-value pre,
|
||||
.jsondiffpatch-textdiff-deleted {
|
||||
background: $red-500;
|
||||
}
|
||||
.jsondiffpatch-added .jsondiffpatch-property-name,
|
||||
.jsondiffpatch-added .jsondiffpatch-value pre,
|
||||
.jsondiffpatch-modified .jsondiffpatch-right-value pre,
|
||||
.jsondiffpatch-textdiff-added {
|
||||
background: $green-500;
|
||||
}
|
||||
|
||||
.jsondiffpatch-property-name {
|
||||
color: $ui-gray-300;
|
||||
}
|
|
@ -118,3 +118,14 @@
|
|||
margin: 0 $spacing-xs;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.version-diff-toolbar {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: $spacing-s;
|
||||
|
||||
.diff-status {
|
||||
display: flex;
|
||||
direction: rtl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
@import './components/confirm';
|
||||
@import './components/console-ui-panel';
|
||||
@import './components/control-group';
|
||||
@import './components/diff-version-selector';
|
||||
@import './components/doc-link';
|
||||
@import './components/empty-state';
|
||||
@import './components/env-banner';
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<Toolbar>
|
||||
<div class="version-diff-toolbar" data-test-version-diff-toolbar>
|
||||
{{!-- Left side version --}}
|
||||
<BasicDropdown
|
||||
@class="popup-menu"
|
||||
@horizontalPosition="auto-right"
|
||||
@verticalPosition="below"
|
||||
as |D|
|
||||
>
|
||||
<D.trigger
|
||||
data-test-popup-menu-trigger="left-version"
|
||||
@class={{concat "toolbar-link" (if D.isOpen " is-active")}}
|
||||
@tagName="button"
|
||||
>
|
||||
Version {{or this.leftSideVersionSelected this.args.model.currentVersion}}
|
||||
<Chevron @direction="down" @isButton={{true}} />
|
||||
</D.trigger>
|
||||
<D.content @class="popup-menu-content">
|
||||
<nav class="box menu">
|
||||
<ul class="menu-list">
|
||||
{{#each (reverse this.args.model.versions) as |leftSideSecretVersion|}}
|
||||
<li class="action" data-test-leftSide-version={{leftSideSecretVersion.version}}>
|
||||
<button
|
||||
class="link"
|
||||
{{on "click" (fn this.selectVersion leftSideSecretVersion.version D.actions "left")}}
|
||||
>
|
||||
Version {{leftSideSecretVersion.version}}
|
||||
{{#if (and (eq leftSideSecretVersion.version (or this.leftSideVersionSelected this.args.model.currentVersion)) (not leftSideSecretVersion.destroyed) (not leftSideSecretVersion.deleted))}}
|
||||
<Icon @glyph="check-circle-outline" class="has-text-success is-pulled-right" />
|
||||
{{else if leftSideSecretVersion.destroyed}}
|
||||
<Icon @glyph="cancel-square-fill" class="has-text-danger is-pulled-right" />
|
||||
{{else if leftSideSecretVersion.deleted}}
|
||||
<Icon @glyph="cancel-square-fill" class="has-text-grey is-pulled-right" />
|
||||
{{/if}}
|
||||
</button>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</nav>
|
||||
</D.content>
|
||||
</BasicDropdown>
|
||||
{{!-- Right side version --}}
|
||||
<BasicDropdown
|
||||
@class="popup-menu"
|
||||
@horizontalPosition="right"
|
||||
@verticalPosition="below"
|
||||
as |D|
|
||||
>
|
||||
<D.trigger
|
||||
@class={{concat "toolbar-link" (if D.isOpen " is-active")}}
|
||||
@tagName="button"
|
||||
data-test-popup-menu-trigger="right-version"
|
||||
>
|
||||
Version {{or this.rightSideVersionSelected this.rightSideVersionInit}}
|
||||
<Chevron @direction="down" @isButton={{true}} />
|
||||
</D.trigger>
|
||||
<D.content @class="popup-menu-content">
|
||||
<nav class="box menu">
|
||||
<ul class="menu-list">
|
||||
{{#each (reverse this.args.model.versions) as |rightSideSecretVersion|}}
|
||||
<li class="action">
|
||||
<button
|
||||
class="link"
|
||||
{{on "click" (fn this.selectVersion rightSideSecretVersion.version D.actions "right")}}
|
||||
data-test-rightSide-version={{rightSideSecretVersion.version}}
|
||||
>
|
||||
Version {{rightSideSecretVersion.version}}
|
||||
{{#if (and (eq rightSideSecretVersion.version (or this.rightSideVersionSelected this.rightSideVersionInit)) (not rightSideSecretVersion.destroyed) (not rightSideSecretVersion.deleted))}}
|
||||
<Icon @glyph="check-circle-outline" class="has-text-success is-pulled-right" />
|
||||
{{else if rightSideSecretVersion.destroyed}}
|
||||
<Icon @glyph="cancel-square-fill" class="has-text-danger is-pulled-right" />
|
||||
{{else if rightSideSecretVersion.deleted}}
|
||||
<Icon @glyph="cancel-square-fill" class="has-text-grey is-pulled-right" />
|
||||
{{/if}}
|
||||
</button>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</nav>
|
||||
</D.content>
|
||||
</BasicDropdown>
|
||||
{{!-- Status --}}
|
||||
{{#if this.statesMatch}}
|
||||
<div class="diff-status">
|
||||
<span>States match</span>
|
||||
<Icon @glyph="check-circle-fill" class="has-text-success" />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</Toolbar>
|
||||
|
||||
<div class="form-section visual-diff">
|
||||
<pre>{{{this.visualDiff}}}</pre>
|
||||
</div>
|
|
@ -1,32 +1,36 @@
|
|||
{{#if showToolbar }}
|
||||
<div data-test-component="json-editor-toolbar">
|
||||
<Toolbar>
|
||||
<label class="is-label" data-test-component="json-editor-title">
|
||||
{{title}}
|
||||
{{#if subTitle }}
|
||||
<span class="is-size-9 is-lowercase has-text-grey">({{ subTitle }})</span>
|
||||
{{/if}}
|
||||
</label>
|
||||
<ToolbarActions>
|
||||
{{yield}}
|
||||
<div class="toolbar-separator"></div>
|
||||
<CopyButton class="button is-transparent" @clipboardText={{value}}
|
||||
@buttonType="button" @success={{action (set-flash-message 'Data copied!')}}>
|
||||
<Icon @glyph="copy-action" aria-label="Copy" />
|
||||
</CopyButton>
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{ivy-codemirror
|
||||
data-test-component="json-editor"
|
||||
value=value
|
||||
options=options
|
||||
valueUpdated=(action "updateValue")
|
||||
onFocusOut=(action "onFocus")
|
||||
}}
|
||||
{{#if helpText }}
|
||||
<div class="box is-shadowless is-fullwidth has-short-padding">
|
||||
<p class="sub-text">{{ helpText }}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div ...attributes>
|
||||
{{#if this.getShowToolbar }}
|
||||
<div data-test-component="json-editor-toolbar">
|
||||
<Toolbar>
|
||||
<label class="is-label" data-test-component="json-editor-title">
|
||||
{{@title}}
|
||||
{{#if @subTitle }}
|
||||
<span class="is-size-9 is-lowercase has-text-grey">({{ @subTitle }})</span>
|
||||
{{/if}}
|
||||
</label>
|
||||
<ToolbarActions>
|
||||
{{yield}}
|
||||
<div class="toolbar-separator"></div>
|
||||
<CopyButton class="button is-transparent" @clipboardText={{@value}}
|
||||
@buttonType="button" @success={{action (set-flash-message 'Data copied!')}}>
|
||||
<Icon @glyph="copy-action" aria-label="Copy" />
|
||||
</CopyButton>
|
||||
</ToolbarActions>
|
||||
</Toolbar>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{ivy-codemirror
|
||||
data-test-component="json-editor"
|
||||
value=@value
|
||||
options=this.options
|
||||
valueUpdated=(action "updateValue")
|
||||
onFocusOut=(action "onFocus")
|
||||
}}
|
||||
|
||||
{{#if @helpText }}
|
||||
<div class="box is-shadowless is-fullwidth has-short-padding">
|
||||
<p class="sub-text">{{ @helpText }}</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -43,6 +43,20 @@
|
|||
View version history
|
||||
</SecretLink>
|
||||
</li>
|
||||
{{#if (gt @model.versions.length 1)}}
|
||||
<li class="action">
|
||||
<li>
|
||||
<LinkTo
|
||||
class="link"
|
||||
@route="vault.cluster.secrets.backend.diff"
|
||||
@model={{@model.id}}
|
||||
data-test-view-diff
|
||||
>
|
||||
View diff
|
||||
</LinkTo>
|
||||
</li>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</D.content>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<PageHeader as |p|>
|
||||
<p.top>
|
||||
<KeyValueHeader
|
||||
@baseKey={{hash id=@model.id}}
|
||||
@path="vault.cluster.secrets.backend.show"
|
||||
@mode="show"
|
||||
@showCurrent={{true}}
|
||||
@root={{backendCrumb}}
|
||||
/>
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3">
|
||||
View diff
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
</PageHeader>
|
||||
|
||||
<DiffVersionSelector
|
||||
@model={{@model}}
|
||||
/>
|
|
@ -71,6 +71,8 @@ module.exports = function(defaults) {
|
|||
app.import('node_modules/codemirror/addon/lint/lint.js');
|
||||
app.import('node_modules/codemirror/addon/lint/json-lint.js');
|
||||
app.import('node_modules/text-encoder-lite/text-encoder-lite.js');
|
||||
app.import('node_modules/jsondiffpatch/dist/jsondiffpatch.umd.js');
|
||||
app.import('node_modules/jsondiffpatch/dist/formatters-styles/html.css');
|
||||
|
||||
app.import('app/styles/bulma/bulma-radio-checkbox.css');
|
||||
|
||||
|
|
|
@ -133,6 +133,7 @@
|
|||
"filesize": "^4.2.1",
|
||||
"flat": "^4.1.0",
|
||||
"ivy-codemirror": "IvyApp/ivy-codemirror#fb09333c5144da47e14a9e6260f80577d5408374",
|
||||
"jsondiffpatch": "^0.4.1",
|
||||
"jsonlint": "^1.6.3",
|
||||
"loader.js": "^4.7.0",
|
||||
"node-forge": "^0.10.0",
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import { click, settled, fillIn } from '@ember/test-helpers';
|
||||
import { create } from 'ember-cli-page-object';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import editPage from 'vault/tests/pages/secrets/backend/kv/edit-secret';
|
||||
import listPage from 'vault/tests/pages/secrets/backend/list';
|
||||
import apiStub from 'vault/tests/helpers/noop-all-api-requests';
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import consoleClass from 'vault/tests/pages/components/console/ui-panel';
|
||||
|
||||
const consoleComponent = create(consoleClass);
|
||||
|
||||
module('Acceptance | kv2 diff view', function(hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
hooks.beforeEach(async function() {
|
||||
this.server = apiStub({ usePassthrough: true });
|
||||
return authPage.login();
|
||||
});
|
||||
|
||||
hooks.afterEach(function() {
|
||||
this.server.shutdown();
|
||||
});
|
||||
|
||||
test('it shows correct diff status based on versions', async function(assert) {
|
||||
const secretPath = `my-secret`;
|
||||
|
||||
await consoleComponent.runCommands([
|
||||
`write sys/mounts/secret type=kv options=version=2`,
|
||||
// delete any kv previously written here so that tests can be re-run
|
||||
`delete secret/metadata/${secretPath}`,
|
||||
'write -field=client_token auth/token/create policies=kv-v2-degrade',
|
||||
]);
|
||||
|
||||
await listPage.visitRoot({ backend: 'secret' });
|
||||
await settled();
|
||||
await listPage.create();
|
||||
await settled();
|
||||
await editPage.createSecret(secretPath, 'version1', 'hello');
|
||||
await settled();
|
||||
await click('[data-test-popup-menu-trigger="version"]');
|
||||
await settled();
|
||||
assert.dom('[data-test-view-diff]').doesNotExist('does not show diff view with only one version');
|
||||
// add another version
|
||||
await click('[data-test-secret-edit="true"]');
|
||||
await settled();
|
||||
|
||||
let secondKey = document.querySelectorAll('[data-test-secret-key]')[1];
|
||||
let secondValue = document.querySelectorAll('.masked-value')[1];
|
||||
await fillIn(secondKey, 'version2');
|
||||
await fillIn(secondValue, 'world!');
|
||||
await click('[data-test-secret-save]');
|
||||
await settled();
|
||||
await click('[data-test-popup-menu-trigger="version"]');
|
||||
await settled();
|
||||
assert.dom('[data-test-view-diff]').exists('does show diff view with two versions');
|
||||
|
||||
await click('[data-test-view-diff]');
|
||||
await settled();
|
||||
let diffBetweenVersion2and1 = document.querySelector('.jsondiffpatch-added').innerText;
|
||||
assert.equal(diffBetweenVersion2and1, 'version2"world!"', 'shows the correct added part');
|
||||
|
||||
await click('[data-test-popup-menu-trigger="right-version"]');
|
||||
await settled();
|
||||
await click('[data-test-rightSide-version="2"]');
|
||||
await settled();
|
||||
assert.dom('.diff-status').exists('shows States Match');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
import EmberObject from '@ember/object';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, click, settled } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
const VERSIONS = [
|
||||
{
|
||||
version: 2,
|
||||
},
|
||||
{
|
||||
version: 1,
|
||||
},
|
||||
];
|
||||
|
||||
module('Integration | Component | diff-version-selector', function(hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function(assert) {
|
||||
this.set(
|
||||
'model',
|
||||
EmberObject.create({
|
||||
currentVersion: 2,
|
||||
versions: VERSIONS,
|
||||
})
|
||||
);
|
||||
await render(hbs`<DiffVersionSelector @model={{this.model}} />`);
|
||||
let leftSideVersion = document
|
||||
.querySelector('[data-test-popup-menu-trigger="left-version"]')
|
||||
.innerText.trim();
|
||||
assert.equal(leftSideVersion, 'Version 2', 'left side toolbar defaults to currentVersion');
|
||||
|
||||
await click('[data-test-popup-menu-trigger="left-version"]');
|
||||
await settled();
|
||||
assert.dom('[data-test-leftSide-version="1"]').exists('leftside shows both versions');
|
||||
assert.dom('[data-test-leftSide-version="2"]').exists('leftside shows both versions');
|
||||
});
|
||||
});
|
13
ui/yarn.lock
13
ui/yarn.lock
|
@ -8540,6 +8540,11 @@ detect-port@^1.3.0:
|
|||
address "^1.0.1"
|
||||
debug "^2.6.0"
|
||||
|
||||
diff-match-patch@^1.0.0:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37"
|
||||
integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==
|
||||
|
||||
diff@^3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
|
@ -13642,6 +13647,14 @@ json5@^2.1.2, json5@^2.1.3:
|
|||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
jsondiffpatch@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jsondiffpatch/-/jsondiffpatch-0.4.1.tgz#9fb085036767f03534ebd46dcd841df6070c5773"
|
||||
integrity sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==
|
||||
dependencies:
|
||||
chalk "^2.3.0"
|
||||
diff-match-patch "^1.0.0"
|
||||
|
||||
jsonfile@^2.1.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
|
||||
|
|
Loading…
Reference in New Issue