mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Complete table tests
This commit is contained in:
parent
18601598d8
commit
0a4b104de1
5 changed files with 409 additions and 39 deletions
|
@ -29,23 +29,28 @@ define(function (require) {
|
|||
if ($scope.mapping[column] && !$scope.mapping[column].indexed) return;
|
||||
|
||||
var sorting = $scope.sorting;
|
||||
var defaultClass = ['fa', 'fa-sort', 'table-header-sortchange'];
|
||||
|
||||
if (!sorting) return [];
|
||||
if (!sorting) return defaultClass;
|
||||
|
||||
if (column === sorting[0]) {
|
||||
return ['fa', sorting[1] === 'asc' ? 'fa-sort-up' : 'fa-sort-down'];
|
||||
} else {
|
||||
return ['fa', 'fa-sort', 'table-header-sortchange'];
|
||||
return defaultClass;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.moveLeft = function (column) {
|
||||
var index = _.indexOf($scope.columns, column);
|
||||
if (index === 0) return;
|
||||
|
||||
_.move($scope.columns, index, --index);
|
||||
};
|
||||
|
||||
$scope.moveRight = function (column) {
|
||||
var index = _.indexOf($scope.columns, column);
|
||||
if (index === $scope.columns.length - 1) return;
|
||||
|
||||
_.move($scope.columns, index, ++index);
|
||||
};
|
||||
|
||||
|
@ -112,7 +117,6 @@ define(function (require) {
|
|||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
fields: '=',
|
||||
columns: '=',
|
||||
filtering: '=',
|
||||
mapping: '=',
|
||||
|
@ -139,27 +143,14 @@ define(function (require) {
|
|||
$scope.maxLength = 250;
|
||||
}
|
||||
|
||||
|
||||
// for now, rows are "tracked" by their index, but this could eventually
|
||||
// be configured so that changing the order of the rows won't prevent
|
||||
// them from staying open on update
|
||||
function rowId(row) {
|
||||
var id = $scope.rows.indexOf(row);
|
||||
return ~id ? id : null;
|
||||
}
|
||||
|
||||
// inverse of rowId()
|
||||
function rowForId(id) {
|
||||
return $scope.rows[id];
|
||||
}
|
||||
|
||||
// toggle display of the rows details, a full list of the fields from each row
|
||||
$scope.toggleRow = function (row, event) {
|
||||
$scope.toggleRow = function () {
|
||||
var row = $scope.row;
|
||||
var id = row._id;
|
||||
|
||||
$scope.open = !$scope.open;
|
||||
|
||||
var $tr = $(event.delegateTarget.parentElement);
|
||||
var $tr = element;
|
||||
var $detailsTr = $tr.next();
|
||||
|
||||
///
|
||||
|
@ -169,7 +160,7 @@ define(function (require) {
|
|||
$detailsTr.toggle($scope.open);
|
||||
|
||||
// Change the caret icon
|
||||
var $toggleIcon = $($(event.delegateTarget).children('i')[0]);
|
||||
var $toggleIcon = $(element.children().first().find('i')[0]);
|
||||
$toggleIcon.toggleClass('fa-caret-down');
|
||||
$toggleIcon.toggleClass('fa-caret-right');
|
||||
|
||||
|
@ -207,7 +198,12 @@ define(function (require) {
|
|||
$scope.filtering(field, row._source[field] || row[field], operation);
|
||||
};
|
||||
|
||||
$scope.$watch('columns', function () {
|
||||
$scope.$watch('columns', function (columns) {
|
||||
element.empty();
|
||||
createSummaryRow($scope.row, $scope.row._id);
|
||||
});
|
||||
|
||||
$scope.$watch('timefield', function (timefield) {
|
||||
element.empty();
|
||||
createSummaryRow($scope.row, $scope.row._id);
|
||||
});
|
||||
|
@ -216,7 +212,7 @@ define(function (require) {
|
|||
function createSummaryRow(row, id) {
|
||||
|
||||
var expandTd = $('<td>').html('<i class="fa fa-caret-right"></span>')
|
||||
.attr('ng-click', 'toggleRow(row, $event)');
|
||||
.attr('ng-click', 'toggleRow()');
|
||||
$compile(expandTd)($scope);
|
||||
element.append(expandTd);
|
||||
|
||||
|
@ -240,11 +236,7 @@ define(function (require) {
|
|||
*/
|
||||
function _displayField(el, row, field, truncate) {
|
||||
var val = _getValForField(row, field, truncate);
|
||||
if (val instanceof DOMNode) {
|
||||
el.append(val);
|
||||
} else {
|
||||
el.text(val);
|
||||
}
|
||||
el.text(val);
|
||||
return el;
|
||||
}
|
||||
|
||||
|
@ -266,17 +258,9 @@ define(function (require) {
|
|||
// undefined and null should just be an empty string
|
||||
val = (val == null) ? '' : val;
|
||||
|
||||
// truncate
|
||||
// truncate the column text, not the details
|
||||
if (typeof val === 'string' && val.length > $scope.maxLength) {
|
||||
if (untruncate) {
|
||||
var complete = val;
|
||||
val = document.createElement('kbn-truncated');
|
||||
val.setAttribute('orig', complete);
|
||||
val.setAttribute('length', $scope.maxLength);
|
||||
val = $compile(val)($scope)[0];// return the actual element
|
||||
} else {
|
||||
val = val.substring(0, $scope.maxLength) + '...';
|
||||
}
|
||||
val = val.substring(0, $scope.maxLength) + '...';
|
||||
}
|
||||
|
||||
return val;
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
<tbody>
|
||||
<tr ng-repeat="row in rows |limitTo:limit track by row._index+row._id"
|
||||
kbn-table-row="row"
|
||||
columns="columns" mapping="mapping" sorting="sorting" timefield="timefield" max-length="maxLength" filtering="filtering"></tr>
|
||||
columns="columns" mapping="mapping" sorting="sorting" timefield="timefield" max-length="maxLength" filtering="filtering"
|
||||
class="discover-table-row"></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<kbn-infinite-scroll more="addRows"></kbn-infinite-scroll>
|
|
@ -3,7 +3,7 @@
|
|||
<span ng-click="sort(timefield)">Time <i ng-class="headerClass(timefield)"></i></span>
|
||||
</th>
|
||||
<th ng-repeat="name in columns">
|
||||
<span ng-click="sort(name)">
|
||||
<span ng-click="sort(name)" class="table-header-name">
|
||||
{{name}} <i ng-class="headerClass(name)"></i>
|
||||
</span>
|
||||
<span class="table-header-move">
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
'kibana',
|
||||
'sinon/sinon',
|
||||
'specs/apps/discover/hit_sort_fn',
|
||||
'specs/apps/discover/directives/table',
|
||||
'specs/directives/confirm-click',
|
||||
'specs/directives/timepicker',
|
||||
'specs/directives/truncate',
|
||||
|
|
|
@ -0,0 +1,384 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
|
||||
// Load the kibana app dependencies.
|
||||
require('angular-route');
|
||||
|
||||
// Load kibana and its applications
|
||||
require('index');
|
||||
|
||||
require('apps/discover/index');
|
||||
|
||||
var $parentScope, $scope, config;
|
||||
|
||||
// Stub out a minimal mapping of 3 fields
|
||||
var mapping = {
|
||||
bytes: {
|
||||
indexed: true,
|
||||
type: 'number'
|
||||
},
|
||||
request: {
|
||||
indexed: false,
|
||||
type: 'string'
|
||||
},
|
||||
timestamp: {
|
||||
indexed: true,
|
||||
type: 'date'
|
||||
},
|
||||
};
|
||||
|
||||
// Sets up the directive, take an element, and a list of properties to attach to the parent scope.
|
||||
var init = function ($elem, props) {
|
||||
module('kibana');
|
||||
inject(function ($rootScope, $compile, _config_) {
|
||||
config = _config_;
|
||||
$parentScope = $rootScope;
|
||||
_.assign($parentScope, props);
|
||||
$compile($elem)($parentScope);
|
||||
$elem.scope().$digest();
|
||||
$scope = $elem.isolateScope();
|
||||
});
|
||||
};
|
||||
|
||||
var destroy = function () {
|
||||
$scope.$destroy();
|
||||
$parentScope.$destroy();
|
||||
};
|
||||
|
||||
// For testing column removing/adding for the header and the rows
|
||||
//
|
||||
var columnTests = function (elemType, parentElem) {
|
||||
it('should create only the toggle column by default', function (done) {
|
||||
var childElems = parentElem.find(elemType);
|
||||
expect(childElems.length).to.be(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create a time column if the timefield is defined', function (done) {
|
||||
// Should include a column for toggling and the time column by default
|
||||
$parentScope.timefield = 'timestamp';
|
||||
parentElem.scope().$digest();
|
||||
var childElems = parentElem.find(elemType);
|
||||
expect(childElems.length).to.be(2);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should be able to add and remove columns', function (done) {
|
||||
var childElems;
|
||||
// Should include a column for toggling and the time column by default
|
||||
$parentScope.columns = ['bytes'];
|
||||
parentElem.scope().$digest();
|
||||
childElems = parentElem.find(elemType);
|
||||
expect(childElems.length).to.be(2);
|
||||
expect($(childElems[1]).text()).to.contain('bytes');
|
||||
|
||||
$parentScope.columns = ['bytes', 'request'];
|
||||
parentElem.scope().$digest();
|
||||
childElems = parentElem.find(elemType);
|
||||
expect(childElems.length).to.be(3);
|
||||
expect($(childElems[2]).text()).to.contain('request');
|
||||
|
||||
$parentScope.columns = ['request'];
|
||||
parentElem.scope().$digest();
|
||||
childElems = parentElem.find(elemType);
|
||||
expect(childElems.length).to.be(2);
|
||||
expect($(childElems[1]).text()).to.contain('request');
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
describe('discover table directives', function () {
|
||||
|
||||
describe('kbnTableHeader', function () {
|
||||
|
||||
var $elem = angular.element(
|
||||
'<thead kbn-table-header columns="columns" mapping="mapping" sort="sort" timefield="timefield"></thead>'
|
||||
);
|
||||
|
||||
beforeEach(function () {
|
||||
init($elem, {
|
||||
mapping: mapping,
|
||||
columns: [],
|
||||
sorting: [],
|
||||
});
|
||||
});
|
||||
afterEach(function () {
|
||||
destroy();
|
||||
});
|
||||
|
||||
describe('adding and removing columns', function () {
|
||||
columnTests('th', $elem);
|
||||
});
|
||||
|
||||
|
||||
describe('sorting', function () {
|
||||
it('should have a sort function that sets the elements of the sort array', function (done) {
|
||||
expect($scope.sort).to.be.a(Function);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should have a headClasser function that determines the css classes of the sort icons', function (done) {
|
||||
expect($scope.headerClass).to.be.a(Function);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should sort asc by default, then by desc if already sorting', function (done) {
|
||||
var field = 'bytes';
|
||||
|
||||
// Should not be sorted at first
|
||||
expect($scope.sorting).to.eql(undefined);
|
||||
expect($scope.headerClass(field)).to.contain('fa-sort');
|
||||
|
||||
$scope.sort(field);
|
||||
expect($scope.sorting).to.eql([field, 'asc']);
|
||||
expect($scope.headerClass(field)).to.contain('fa-sort-up');
|
||||
|
||||
$scope.sort(field);
|
||||
expect($scope.sorting).to.eql([field, 'desc']);
|
||||
expect($scope.headerClass(field)).to.contain('fa-sort-down');
|
||||
|
||||
$scope.sort(field);
|
||||
expect($scope.sorting).to.eql([field, 'asc']);
|
||||
expect($scope.headerClass(field)).to.contain('fa-sort-up');
|
||||
|
||||
// Should show the default sort for any other field
|
||||
expect($scope.headerClass('timestamp')).to.contain('fa-sort');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should NOT sort unindexed fields', function (done) {
|
||||
$scope.sort('request');
|
||||
expect($scope.sorting).to.be(undefined);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('moving columns', function () {
|
||||
beforeEach(function () {
|
||||
$parentScope.columns = _.keys($scope.mapping);
|
||||
$elem.scope().$digest();
|
||||
});
|
||||
|
||||
it('should move columns to the right', function () {
|
||||
|
||||
$scope.moveRight('bytes');
|
||||
expect($scope.columns[1]).to.be('bytes');
|
||||
|
||||
$scope.moveRight('bytes');
|
||||
expect($scope.columns[2]).to.be('bytes');
|
||||
});
|
||||
|
||||
it('shouldnt move the last column to the right', function () {
|
||||
expect($scope.columns[2]).to.be('timestamp');
|
||||
|
||||
$scope.moveRight('timestamp');
|
||||
expect($scope.columns[2]).to.be('timestamp');
|
||||
});
|
||||
|
||||
it('should move columns to the left', function () {
|
||||
$scope.moveLeft('timestamp');
|
||||
expect($scope.columns[1]).to.be('timestamp');
|
||||
|
||||
$scope.moveLeft('request');
|
||||
expect($scope.columns[1]).to.be('request');
|
||||
});
|
||||
|
||||
it('shouldnt move the first column to the left', function () {
|
||||
expect($scope.columns[0]).to.be('bytes');
|
||||
|
||||
$scope.moveLeft('bytes');
|
||||
expect($scope.columns[0]).to.be('bytes');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var longString = Array(50).join('_');
|
||||
|
||||
var getFakeRow = function (id) {
|
||||
var columns = _.keys(mapping);
|
||||
return {
|
||||
_formatted: _.zipObject(_.map(columns, function (c) { return [c, c + '_formatted_' + id + longString]; })),
|
||||
_source: _.zipObject(_.map(columns, function (c) { return [c, c + '_original_' + id + longString]; })),
|
||||
_id: id,
|
||||
_index: 'test',
|
||||
sort: [id]
|
||||
};
|
||||
};
|
||||
|
||||
describe('kbnTable', function () {
|
||||
|
||||
var $elem = angular.element(
|
||||
'<kbn-table ' +
|
||||
'columns="columns" ' +
|
||||
'rows="rows" ' +
|
||||
'sorting="sorting"' +
|
||||
'filtering="filtering"' +
|
||||
'maxLength=maxLength ' +
|
||||
'mapping="mapping"' +
|
||||
'timefield="timefield" ' +
|
||||
'></thead>'
|
||||
);
|
||||
|
||||
beforeEach(function () {
|
||||
|
||||
// A tiny window
|
||||
sinon.stub($.prototype, 'height', function () { return 100; });
|
||||
|
||||
// Convince the infinite scroll that there's still a lot of room left.
|
||||
sinon.stub($.prototype, 'scrollTop', function () { return -200; });
|
||||
|
||||
var rows = _.times(200, function (i) {
|
||||
return getFakeRow(i);
|
||||
});
|
||||
init($elem, {
|
||||
columns: ['bytes'],
|
||||
rows: rows,
|
||||
sorting: [],
|
||||
filtering: sinon.spy(),
|
||||
maxLength: 50,
|
||||
mapping: mapping,
|
||||
timefield: 'timestamp'
|
||||
});
|
||||
});
|
||||
afterEach(function () {
|
||||
destroy();
|
||||
});
|
||||
|
||||
it('should have a header and a table element', function (done) {
|
||||
expect($elem.find('thead').length).to.be(1);
|
||||
expect($elem.find('table').length).to.be(1);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('should have 50 rows to start', function (done) {
|
||||
var tr = $elem.find('.discover-table-row');
|
||||
expect(tr.length).to.be(50);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should have an addRows function that adds 50 rows', function (done) {
|
||||
expect($scope.addRows).to.be.a(Function);
|
||||
$scope.addRows();
|
||||
$elem.scope().$digest();
|
||||
|
||||
var tr = $elem.find('.discover-table-row');
|
||||
expect(tr.length).to.be(100);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('kbnTableRow', function () {
|
||||
|
||||
var $elem = angular.element(
|
||||
'<tr kbn-table-row="row" ' +
|
||||
'columns="columns" ' +
|
||||
'sorting="sorting"' +
|
||||
'filtering="filtering"' +
|
||||
'max-length=maxLength ' +
|
||||
'mapping="mapping"' +
|
||||
'timefield="timefield" ' +
|
||||
'></tr>'
|
||||
);
|
||||
|
||||
beforeEach(function () {
|
||||
|
||||
init($elem, {
|
||||
row: getFakeRow(0),
|
||||
columns: [],
|
||||
sorting: [],
|
||||
filtering: sinon.spy(),
|
||||
maxLength: 50,
|
||||
mapping: mapping,
|
||||
});
|
||||
|
||||
// Ignore the metaFields (_id, _type, etc) since we don't have a mapping for them
|
||||
sinon.stub(config, 'get').withArgs('metaFields').returns([]);
|
||||
|
||||
});
|
||||
afterEach(function () {
|
||||
destroy();
|
||||
});
|
||||
|
||||
describe('adding and removing columns', function () {
|
||||
columnTests('td', $elem);
|
||||
});
|
||||
|
||||
describe('details row', function () {
|
||||
it('should be an empty tr by default', function () {
|
||||
expect($elem.next().is('tr')).to.be(true);
|
||||
expect($elem.next().text()).to.be('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('details row', function () {
|
||||
it('should be an empty tr by default', function () {
|
||||
expect($elem.next().is('tr')).to.be(true);
|
||||
expect($elem.next().text()).to.be('');
|
||||
});
|
||||
|
||||
it('should expand the detail row when the toggle arrow is clicked', function () {
|
||||
$elem.children(':first-child').click();
|
||||
$scope.$digest();
|
||||
expect($elem.next().text()).to.not.be('');
|
||||
});
|
||||
|
||||
describe('expanded', function () {
|
||||
var $details;
|
||||
beforeEach(function () {
|
||||
// Open the row
|
||||
$scope.toggleRow();
|
||||
$scope.$digest();
|
||||
$details = $elem.next();
|
||||
});
|
||||
afterEach(function () {
|
||||
// Close the row
|
||||
$scope.toggleRow();
|
||||
$scope.$digest();
|
||||
});
|
||||
|
||||
it('should be a tr', function () {
|
||||
expect($details.is('tr')).to.be(true);
|
||||
});
|
||||
|
||||
it('should have a row for each field', function () {
|
||||
var rows = $details.find('tr');
|
||||
var row = $scope.row;
|
||||
expect($details.find('tr').length).to.be(3);
|
||||
});
|
||||
|
||||
it('should have a row for each field', function () {
|
||||
var rows = $details.find('tr');
|
||||
var row = $scope.row;
|
||||
expect($details.find('tr').length).to.be(3);
|
||||
});
|
||||
|
||||
describe('filtering', function () {
|
||||
it('should filter when you click on the filter buttons', function () {
|
||||
$details.find('.fa-search-plus').first().click();
|
||||
expect($scope.filtering.calledOnce).to.be(true);
|
||||
$details.find('.fa-search-minus').first().click();
|
||||
expect($scope.filtering.calledTwice).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue