mirror of
https://github.com/flutter/samples.git
synced 2026-03-30 16:23:23 +00:00
Add flutter_web samples (#75)
This commit is contained in:
committed by
Andrew Brogdon
parent
42f2dce01b
commit
3fe927cb29
@@ -0,0 +1,253 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'dart:math' show Rectangle;
|
||||
import 'package:charts_common/src/chart/common/chart_context.dart';
|
||||
import 'package:charts_common/src/chart/common/processed_series.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/axis.dart';
|
||||
import 'package:charts_common/src/chart/common/behavior/a11y/domain_a11y_explore_behavior.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/cartesian_chart.dart';
|
||||
import 'package:charts_common/src/data/series.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockContext extends Mock implements ChartContext {}
|
||||
|
||||
class MockAxis extends Mock implements Axis<String> {}
|
||||
|
||||
class FakeCartesianChart extends CartesianChart<String> {
|
||||
@override
|
||||
Rectangle<int> drawAreaBounds;
|
||||
|
||||
void callFireOnPostprocess(List<MutableSeries<String>> seriesList) {
|
||||
fireOnPostprocess(seriesList);
|
||||
}
|
||||
|
||||
@override
|
||||
initDomainAxis() {}
|
||||
}
|
||||
|
||||
void main() {
|
||||
FakeCartesianChart chart;
|
||||
DomainA11yExploreBehavior<String> behavior;
|
||||
MockAxis domainAxis;
|
||||
|
||||
MutableSeries<String> _series1;
|
||||
final _s1D1 = new MyRow('s1d1', 11, 'a11yd1');
|
||||
final _s1D2 = new MyRow('s1d2', 12, 'a11yd2');
|
||||
final _s1D3 = new MyRow('s1d3', 13, 'a11yd3');
|
||||
|
||||
setUp(() {
|
||||
chart = new FakeCartesianChart()
|
||||
..drawAreaBounds = new Rectangle(50, 20, 150, 80);
|
||||
|
||||
behavior = new DomainA11yExploreBehavior<String>(
|
||||
vocalizationCallback: domainVocalization);
|
||||
behavior.attachTo(chart);
|
||||
|
||||
domainAxis = new MockAxis();
|
||||
_series1 = new MutableSeries(new Series<MyRow, String>(
|
||||
id: 's1',
|
||||
data: [_s1D1, _s1D2, _s1D3],
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.count,
|
||||
))
|
||||
..setAttr(domainAxisKey, domainAxis);
|
||||
});
|
||||
|
||||
test('creates nodes for vertically drawn charts', () {
|
||||
// A LTR chart
|
||||
final context = new MockContext();
|
||||
when(context.chartContainerIsRtl).thenReturn(false);
|
||||
when(context.isRtl).thenReturn(false);
|
||||
chart.context = context;
|
||||
// Drawn vertically
|
||||
chart.vertical = true;
|
||||
// Set step size of 50, which should be the width of the bounding box
|
||||
when(domainAxis.stepSize).thenReturn(50.0);
|
||||
when(domainAxis.getLocation('s1d1')).thenReturn(75.0);
|
||||
when(domainAxis.getLocation('s1d2')).thenReturn(125.0);
|
||||
when(domainAxis.getLocation('s1d3')).thenReturn(175.0);
|
||||
// Call fire on post process for the behavior to get the series list.
|
||||
chart.callFireOnPostprocess([_series1]);
|
||||
|
||||
final nodes = behavior.createA11yNodes();
|
||||
|
||||
expect(nodes, hasLength(3));
|
||||
expect(nodes[0].label, equals('s1d1'));
|
||||
expect(nodes[0].boundingBox, equals(new Rectangle(50, 20, 50, 80)));
|
||||
expect(nodes[1].label, equals('s1d2'));
|
||||
expect(nodes[1].boundingBox, equals(new Rectangle(100, 20, 50, 80)));
|
||||
expect(nodes[2].label, equals('s1d3'));
|
||||
expect(nodes[2].boundingBox, equals(new Rectangle(150, 20, 50, 80)));
|
||||
});
|
||||
|
||||
test('creates nodes for vertically drawn RTL charts', () {
|
||||
// A RTL chart
|
||||
final context = new MockContext();
|
||||
when(context.chartContainerIsRtl).thenReturn(true);
|
||||
when(context.isRtl).thenReturn(true);
|
||||
chart.context = context;
|
||||
// Drawn vertically
|
||||
chart.vertical = true;
|
||||
// Set step size of 50, which should be the width of the bounding box
|
||||
when(domainAxis.stepSize).thenReturn(50.0);
|
||||
when(domainAxis.getLocation('s1d1')).thenReturn(175.0);
|
||||
when(domainAxis.getLocation('s1d2')).thenReturn(125.0);
|
||||
when(domainAxis.getLocation('s1d3')).thenReturn(75.0);
|
||||
// Call fire on post process for the behavior to get the series list.
|
||||
chart.callFireOnPostprocess([_series1]);
|
||||
|
||||
final nodes = behavior.createA11yNodes();
|
||||
|
||||
expect(nodes, hasLength(3));
|
||||
expect(nodes[0].label, equals('s1d1'));
|
||||
expect(nodes[0].boundingBox, equals(new Rectangle(150, 20, 50, 80)));
|
||||
expect(nodes[1].label, equals('s1d2'));
|
||||
expect(nodes[1].boundingBox, equals(new Rectangle(100, 20, 50, 80)));
|
||||
expect(nodes[2].label, equals('s1d3'));
|
||||
expect(nodes[2].boundingBox, equals(new Rectangle(50, 20, 50, 80)));
|
||||
});
|
||||
|
||||
test('creates nodes for horizontally drawn charts', () {
|
||||
// A LTR chart
|
||||
final context = new MockContext();
|
||||
when(context.chartContainerIsRtl).thenReturn(false);
|
||||
when(context.isRtl).thenReturn(false);
|
||||
chart.context = context;
|
||||
// Drawn horizontally
|
||||
chart.vertical = false;
|
||||
// Set step size of 20, which should be the height of the bounding box
|
||||
when(domainAxis.stepSize).thenReturn(20.0);
|
||||
when(domainAxis.getLocation('s1d1')).thenReturn(30.0);
|
||||
when(domainAxis.getLocation('s1d2')).thenReturn(50.0);
|
||||
when(domainAxis.getLocation('s1d3')).thenReturn(70.0);
|
||||
// Call fire on post process for the behavior to get the series list.
|
||||
chart.callFireOnPostprocess([_series1]);
|
||||
|
||||
final nodes = behavior.createA11yNodes();
|
||||
|
||||
expect(nodes, hasLength(3));
|
||||
expect(nodes[0].label, equals('s1d1'));
|
||||
expect(nodes[0].boundingBox, equals(new Rectangle(50, 20, 150, 20)));
|
||||
expect(nodes[1].label, equals('s1d2'));
|
||||
expect(nodes[1].boundingBox, equals(new Rectangle(50, 40, 150, 20)));
|
||||
expect(nodes[2].label, equals('s1d3'));
|
||||
expect(nodes[2].boundingBox, equals(new Rectangle(50, 60, 150, 20)));
|
||||
});
|
||||
|
||||
test('creates nodes for horizontally drawn RTL charts', () {
|
||||
// A LTR chart
|
||||
final context = new MockContext();
|
||||
when(context.chartContainerIsRtl).thenReturn(true);
|
||||
when(context.isRtl).thenReturn(true);
|
||||
chart.context = context;
|
||||
// Drawn horizontally
|
||||
chart.vertical = false;
|
||||
// Set step size of 20, which should be the height of the bounding box
|
||||
when(domainAxis.stepSize).thenReturn(20.0);
|
||||
when(domainAxis.getLocation('s1d1')).thenReturn(30.0);
|
||||
when(domainAxis.getLocation('s1d2')).thenReturn(50.0);
|
||||
when(domainAxis.getLocation('s1d3')).thenReturn(70.0);
|
||||
// Call fire on post process for the behavior to get the series list.
|
||||
chart.callFireOnPostprocess([_series1]);
|
||||
|
||||
final nodes = behavior.createA11yNodes();
|
||||
|
||||
expect(nodes, hasLength(3));
|
||||
expect(nodes[0].label, equals('s1d1'));
|
||||
expect(nodes[0].boundingBox, equals(new Rectangle(50, 20, 150, 20)));
|
||||
expect(nodes[1].label, equals('s1d2'));
|
||||
expect(nodes[1].boundingBox, equals(new Rectangle(50, 40, 150, 20)));
|
||||
expect(nodes[2].label, equals('s1d3'));
|
||||
expect(nodes[2].boundingBox, equals(new Rectangle(50, 60, 150, 20)));
|
||||
});
|
||||
|
||||
test('nodes ordered correctly with a series missing a domain', () {
|
||||
// A LTR chart
|
||||
final context = new MockContext();
|
||||
when(context.chartContainerIsRtl).thenReturn(false);
|
||||
when(context.isRtl).thenReturn(false);
|
||||
chart.context = context;
|
||||
// Drawn vertically
|
||||
chart.vertical = true;
|
||||
// Set step size of 50, which should be the width of the bounding box
|
||||
when(domainAxis.stepSize).thenReturn(50.0);
|
||||
when(domainAxis.getLocation('s1d1')).thenReturn(75.0);
|
||||
when(domainAxis.getLocation('s1d2')).thenReturn(125.0);
|
||||
when(domainAxis.getLocation('s1d3')).thenReturn(175.0);
|
||||
// Create a series with a missing domain
|
||||
final seriesWithMissingDomain = new MutableSeries(new Series<MyRow, String>(
|
||||
id: 'm1',
|
||||
data: [_s1D1, _s1D3],
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.count,
|
||||
))
|
||||
..setAttr(domainAxisKey, domainAxis);
|
||||
|
||||
// Call fire on post process for the behavior to get the series list.
|
||||
chart.callFireOnPostprocess([seriesWithMissingDomain, _series1]);
|
||||
|
||||
final nodes = behavior.createA11yNodes();
|
||||
|
||||
expect(nodes, hasLength(3));
|
||||
expect(nodes[0].label, equals('s1d1'));
|
||||
expect(nodes[0].boundingBox, equals(new Rectangle(50, 20, 50, 80)));
|
||||
expect(nodes[1].label, equals('s1d2'));
|
||||
expect(nodes[1].boundingBox, equals(new Rectangle(100, 20, 50, 80)));
|
||||
expect(nodes[2].label, equals('s1d3'));
|
||||
expect(nodes[2].boundingBox, equals(new Rectangle(150, 20, 50, 80)));
|
||||
});
|
||||
|
||||
test('creates nodes with minimum width', () {
|
||||
// A behavior with minimum width of 50
|
||||
final behaviorWithMinWidth =
|
||||
new DomainA11yExploreBehavior<String>(minimumWidth: 50.0);
|
||||
behaviorWithMinWidth.attachTo(chart);
|
||||
|
||||
// A LTR chart
|
||||
final context = new MockContext();
|
||||
when(context.chartContainerIsRtl).thenReturn(false);
|
||||
when(context.isRtl).thenReturn(false);
|
||||
chart.context = context;
|
||||
// Drawn vertically
|
||||
chart.vertical = true;
|
||||
// Return a step size of 20, which is less than the minimum width.
|
||||
// Expect the results to use the minimum width of 50 instead.
|
||||
when(domainAxis.stepSize).thenReturn(20.0);
|
||||
when(domainAxis.getLocation('s1d1')).thenReturn(75.0);
|
||||
when(domainAxis.getLocation('s1d2')).thenReturn(125.0);
|
||||
when(domainAxis.getLocation('s1d3')).thenReturn(175.0);
|
||||
// Call fire on post process for the behavior to get the series list.
|
||||
chart.callFireOnPostprocess([_series1]);
|
||||
|
||||
final nodes = behaviorWithMinWidth.createA11yNodes();
|
||||
|
||||
expect(nodes, hasLength(3));
|
||||
expect(nodes[0].label, equals('s1d1'));
|
||||
expect(nodes[0].boundingBox, equals(new Rectangle(50, 20, 50, 80)));
|
||||
expect(nodes[1].label, equals('s1d2'));
|
||||
expect(nodes[1].boundingBox, equals(new Rectangle(100, 20, 50, 80)));
|
||||
expect(nodes[2].label, equals('s1d3'));
|
||||
expect(nodes[2].boundingBox, equals(new Rectangle(150, 20, 50, 80)));
|
||||
});
|
||||
}
|
||||
|
||||
class MyRow {
|
||||
final String campaign;
|
||||
final int count;
|
||||
final String a11yDescription;
|
||||
MyRow(this.campaign, this.count, this.a11yDescription);
|
||||
}
|
||||
@@ -0,0 +1,593 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'package:charts_common/src/chart/cartesian/cartesian_chart.dart';
|
||||
import 'package:charts_common/src/chart/common/base_chart.dart';
|
||||
import 'package:charts_common/src/chart/common/processed_series.dart'
|
||||
show MutableSeries;
|
||||
import 'package:charts_common/src/chart/common/behavior/calculation/percent_injector.dart';
|
||||
import 'package:charts_common/src/data/series.dart' show Series;
|
||||
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
/// Datum/Row for the chart.
|
||||
class MyRow {
|
||||
final String campaign;
|
||||
final int clickCount;
|
||||
final int clickCountLower;
|
||||
final int clickCountUpper;
|
||||
MyRow(this.campaign, this.clickCount, this.clickCountLower,
|
||||
this.clickCountUpper);
|
||||
}
|
||||
|
||||
class MockChart extends Mock implements CartesianChart {
|
||||
LifecycleListener lastLifecycleListener;
|
||||
|
||||
bool vertical = true;
|
||||
|
||||
@override
|
||||
addLifecycleListener(LifecycleListener listener) =>
|
||||
lastLifecycleListener = listener;
|
||||
|
||||
@override
|
||||
removeLifecycleListener(LifecycleListener listener) {
|
||||
expect(listener, equals(lastLifecycleListener));
|
||||
lastLifecycleListener = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
MockChart _chart;
|
||||
List<MutableSeries<String>> seriesList;
|
||||
|
||||
PercentInjector _makeBehavior(
|
||||
{PercentInjectorTotalType totalType = PercentInjectorTotalType.domain}) {
|
||||
final behavior = new PercentInjector(totalType: totalType);
|
||||
|
||||
behavior.attachTo(_chart);
|
||||
|
||||
return behavior;
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
_chart = new MockChart();
|
||||
|
||||
final myFakeDesktopAData = [
|
||||
new MyRow('MyCampaign1', 1, 1, 1),
|
||||
new MyRow('MyCampaign2', 2, 2, 2),
|
||||
new MyRow('MyCampaign3', 3, 3, 3),
|
||||
];
|
||||
|
||||
final myFakeTabletAData = [
|
||||
new MyRow('MyCampaign1', 2, 2, 2),
|
||||
new MyRow('MyCampaign2', 3, 3, 3),
|
||||
new MyRow('MyCampaign3', 4, 4, 4),
|
||||
];
|
||||
|
||||
final myFakeMobileAData = [
|
||||
new MyRow('MyCampaign1', 3, 3, 3),
|
||||
new MyRow('MyCampaign2', 4, 4, 4),
|
||||
new MyRow('MyCampaign3', 5, 5, 5),
|
||||
];
|
||||
|
||||
final myFakeDesktopBData = [
|
||||
new MyRow('MyCampaign1', 10, 8, 12),
|
||||
new MyRow('MyCampaign2', 20, 18, 22),
|
||||
new MyRow('MyCampaign3', 30, 28, 32),
|
||||
];
|
||||
|
||||
final myFakeTabletBData = [
|
||||
new MyRow('MyCampaign1', 20, 18, 22),
|
||||
new MyRow('MyCampaign2', 30, 28, 32),
|
||||
new MyRow('MyCampaign3', 40, 38, 42),
|
||||
];
|
||||
|
||||
final myFakeMobileBData = [
|
||||
new MyRow('MyCampaign1', 30, 28, 32),
|
||||
new MyRow('MyCampaign2', 40, 38, 42),
|
||||
new MyRow('MyCampaign3', 50, 48, 52),
|
||||
];
|
||||
|
||||
seriesList = [
|
||||
new MutableSeries<String>(new Series<MyRow, String>(
|
||||
id: 'Desktop A',
|
||||
seriesCategory: 'A',
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.clickCount,
|
||||
measureOffsetFn: (MyRow row, _) => 0,
|
||||
data: myFakeDesktopAData)),
|
||||
new MutableSeries<String>(new Series<MyRow, String>(
|
||||
id: 'Tablet A',
|
||||
seriesCategory: 'A',
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.clickCount,
|
||||
measureOffsetFn: (MyRow row, _) => 0,
|
||||
data: myFakeTabletAData)),
|
||||
new MutableSeries<String>(new Series<MyRow, String>(
|
||||
id: 'Mobile A',
|
||||
seriesCategory: 'A',
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.clickCount,
|
||||
measureOffsetFn: (MyRow row, _) => 0,
|
||||
data: myFakeMobileAData)),
|
||||
new MutableSeries<String>(new Series<MyRow, String>(
|
||||
id: 'Desktop B',
|
||||
seriesCategory: 'B',
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.clickCount,
|
||||
measureLowerBoundFn: (MyRow row, _) => row.clickCountLower,
|
||||
measureUpperBoundFn: (MyRow row, _) => row.clickCountUpper,
|
||||
measureOffsetFn: (MyRow row, _) => 0,
|
||||
data: myFakeDesktopBData)),
|
||||
new MutableSeries<String>(new Series<MyRow, String>(
|
||||
id: 'Tablet B',
|
||||
seriesCategory: 'B',
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.clickCount,
|
||||
measureLowerBoundFn: (MyRow row, _) => row.clickCountLower,
|
||||
measureUpperBoundFn: (MyRow row, _) => row.clickCountUpper,
|
||||
measureOffsetFn: (MyRow row, _) => 0,
|
||||
data: myFakeTabletBData)),
|
||||
new MutableSeries<String>(new Series<MyRow, String>(
|
||||
id: 'Mobile B',
|
||||
seriesCategory: 'B',
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.clickCount,
|
||||
measureLowerBoundFn: (MyRow row, _) => row.clickCountLower,
|
||||
measureUpperBoundFn: (MyRow row, _) => row.clickCountUpper,
|
||||
measureOffsetFn: (MyRow row, _) => 0,
|
||||
data: myFakeMobileBData))
|
||||
];
|
||||
});
|
||||
|
||||
group('Inject', () {
|
||||
test('percent of domain', () {
|
||||
// Setup behavior.
|
||||
_makeBehavior(totalType: PercentInjectorTotalType.domain);
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onData(seriesList);
|
||||
_chart.lastLifecycleListener.onPreprocess(seriesList);
|
||||
|
||||
// Verify first series.
|
||||
var series = seriesList[0];
|
||||
|
||||
expect(series.measureFn(0), equals(1 / 66));
|
||||
expect(series.measureFn(1), equals(2 / 99));
|
||||
expect(series.measureFn(2), equals(3 / 132));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(1));
|
||||
expect(series.rawMeasureFn(1), equals(2));
|
||||
expect(series.rawMeasureFn(2), equals(3));
|
||||
|
||||
// Verify second series.
|
||||
series = seriesList[1];
|
||||
|
||||
expect(series.measureFn(0), equals(2 / 66));
|
||||
expect(series.measureFn(1), equals(3 / 99));
|
||||
expect(series.measureFn(2), equals(4 / 132));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(2));
|
||||
expect(series.rawMeasureFn(1), equals(3));
|
||||
expect(series.rawMeasureFn(2), equals(4));
|
||||
|
||||
// Verify third series.
|
||||
series = seriesList[2];
|
||||
|
||||
expect(series.measureFn(0), equals(3 / 66));
|
||||
expect(series.measureFn(1), equals(4 / 99));
|
||||
expect(series.measureFn(2), equals(5 / 132));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(3));
|
||||
expect(series.rawMeasureFn(1), equals(4));
|
||||
expect(series.rawMeasureFn(2), equals(5));
|
||||
|
||||
// Verify fourth series.
|
||||
series = seriesList[3];
|
||||
|
||||
expect(series.measureFn(0), equals(10 / 66));
|
||||
expect(series.measureFn(1), equals(20 / 99));
|
||||
expect(series.measureFn(2), equals(30 / 132));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(10));
|
||||
expect(series.rawMeasureFn(1), equals(20));
|
||||
expect(series.rawMeasureFn(2), equals(30));
|
||||
|
||||
expect(series.measureLowerBoundFn(0), equals(8 / 66));
|
||||
expect(series.measureLowerBoundFn(1), equals(18 / 99));
|
||||
expect(series.measureLowerBoundFn(2), equals(28 / 132));
|
||||
|
||||
expect(series.rawMeasureLowerBoundFn(0), equals(8));
|
||||
expect(series.rawMeasureLowerBoundFn(1), equals(18));
|
||||
expect(series.rawMeasureLowerBoundFn(2), equals(28));
|
||||
|
||||
expect(series.measureUpperBoundFn(0), equals(12 / 66));
|
||||
expect(series.measureUpperBoundFn(1), equals(22 / 99));
|
||||
expect(series.measureUpperBoundFn(2), equals(32 / 132));
|
||||
|
||||
expect(series.rawMeasureUpperBoundFn(0), equals(12));
|
||||
expect(series.rawMeasureUpperBoundFn(1), equals(22));
|
||||
expect(series.rawMeasureUpperBoundFn(2), equals(32));
|
||||
|
||||
// Verify fifth series.
|
||||
series = seriesList[4];
|
||||
|
||||
expect(series.measureFn(0), equals(20 / 66));
|
||||
expect(series.measureFn(1), equals(30 / 99));
|
||||
expect(series.measureFn(2), equals(40 / 132));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(20));
|
||||
expect(series.rawMeasureFn(1), equals(30));
|
||||
expect(series.rawMeasureFn(2), equals(40));
|
||||
|
||||
expect(series.measureLowerBoundFn(0), equals(18 / 66));
|
||||
expect(series.measureLowerBoundFn(1), equals(28 / 99));
|
||||
expect(series.measureLowerBoundFn(2), equals(38 / 132));
|
||||
|
||||
expect(series.rawMeasureLowerBoundFn(0), equals(18));
|
||||
expect(series.rawMeasureLowerBoundFn(1), equals(28));
|
||||
expect(series.rawMeasureLowerBoundFn(2), equals(38));
|
||||
|
||||
expect(series.measureUpperBoundFn(0), equals(22 / 66));
|
||||
expect(series.measureUpperBoundFn(1), equals(32 / 99));
|
||||
expect(series.measureUpperBoundFn(2), equals(42 / 132));
|
||||
|
||||
expect(series.rawMeasureUpperBoundFn(0), equals(22));
|
||||
expect(series.rawMeasureUpperBoundFn(1), equals(32));
|
||||
expect(series.rawMeasureUpperBoundFn(2), equals(42));
|
||||
|
||||
// Verify sixth series.
|
||||
series = seriesList[5];
|
||||
|
||||
expect(series.measureFn(0), equals(30 / 66));
|
||||
expect(series.measureFn(1), equals(40 / 99));
|
||||
expect(series.measureFn(2), equals(50 / 132));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(30));
|
||||
expect(series.rawMeasureFn(1), equals(40));
|
||||
expect(series.rawMeasureFn(2), equals(50));
|
||||
|
||||
expect(series.measureLowerBoundFn(0), equals(28 / 66));
|
||||
expect(series.measureLowerBoundFn(1), equals(38 / 99));
|
||||
expect(series.measureLowerBoundFn(2), equals(48 / 132));
|
||||
|
||||
expect(series.rawMeasureLowerBoundFn(0), equals(28));
|
||||
expect(series.rawMeasureLowerBoundFn(1), equals(38));
|
||||
expect(series.rawMeasureLowerBoundFn(2), equals(48));
|
||||
|
||||
expect(series.measureUpperBoundFn(0), equals(32 / 66));
|
||||
expect(series.measureUpperBoundFn(1), equals(42 / 99));
|
||||
expect(series.measureUpperBoundFn(2), equals(52 / 132));
|
||||
|
||||
expect(series.rawMeasureUpperBoundFn(0), equals(32));
|
||||
expect(series.rawMeasureUpperBoundFn(1), equals(42));
|
||||
expect(series.rawMeasureUpperBoundFn(2), equals(52));
|
||||
});
|
||||
|
||||
test('percent of domain, grouped by series category', () {
|
||||
// Setup behavior.
|
||||
_makeBehavior(totalType: PercentInjectorTotalType.domainBySeriesCategory);
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onData(seriesList);
|
||||
_chart.lastLifecycleListener.onPreprocess(seriesList);
|
||||
|
||||
// Verify first series.
|
||||
var series = seriesList[0];
|
||||
|
||||
expect(series.measureFn(0), equals(1 / 6));
|
||||
expect(series.measureFn(1), equals(2 / 9));
|
||||
expect(series.measureFn(2), equals(3 / 12));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(1));
|
||||
expect(series.rawMeasureFn(1), equals(2));
|
||||
expect(series.rawMeasureFn(2), equals(3));
|
||||
|
||||
// Verify second series.
|
||||
series = seriesList[1];
|
||||
|
||||
expect(series.measureFn(0), equals(2 / 6));
|
||||
expect(series.measureFn(1), equals(3 / 9));
|
||||
expect(series.measureFn(2), equals(4 / 12));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(2));
|
||||
expect(series.rawMeasureFn(1), equals(3));
|
||||
expect(series.rawMeasureFn(2), equals(4));
|
||||
|
||||
// Verify third series.
|
||||
series = seriesList[2];
|
||||
|
||||
expect(series.measureFn(0), equals(3 / 6));
|
||||
expect(series.measureFn(1), equals(4 / 9));
|
||||
expect(series.measureFn(2), equals(5 / 12));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(3));
|
||||
expect(series.rawMeasureFn(1), equals(4));
|
||||
expect(series.rawMeasureFn(2), equals(5));
|
||||
|
||||
// Verify fourth series.
|
||||
series = seriesList[3];
|
||||
|
||||
expect(series.measureFn(0), equals(10 / 60));
|
||||
expect(series.measureFn(1), equals(20 / 90));
|
||||
expect(series.measureFn(2), equals(30 / 120));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(10));
|
||||
expect(series.rawMeasureFn(1), equals(20));
|
||||
expect(series.rawMeasureFn(2), equals(30));
|
||||
|
||||
expect(series.measureLowerBoundFn(0), equals(8 / 60));
|
||||
expect(series.measureLowerBoundFn(1), equals(18 / 90));
|
||||
expect(series.measureLowerBoundFn(2), equals(28 / 120));
|
||||
|
||||
expect(series.rawMeasureLowerBoundFn(0), equals(8));
|
||||
expect(series.rawMeasureLowerBoundFn(1), equals(18));
|
||||
expect(series.rawMeasureLowerBoundFn(2), equals(28));
|
||||
|
||||
expect(series.measureUpperBoundFn(0), equals(12 / 60));
|
||||
expect(series.measureUpperBoundFn(1), equals(22 / 90));
|
||||
expect(series.measureUpperBoundFn(2), equals(32 / 120));
|
||||
|
||||
expect(series.rawMeasureUpperBoundFn(0), equals(12));
|
||||
expect(series.rawMeasureUpperBoundFn(1), equals(22));
|
||||
expect(series.rawMeasureUpperBoundFn(2), equals(32));
|
||||
|
||||
// Verify fifth series.
|
||||
series = seriesList[4];
|
||||
|
||||
expect(series.measureFn(0), equals(20 / 60));
|
||||
expect(series.measureFn(1), equals(30 / 90));
|
||||
expect(series.measureFn(2), equals(40 / 120));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(20));
|
||||
expect(series.rawMeasureFn(1), equals(30));
|
||||
expect(series.rawMeasureFn(2), equals(40));
|
||||
|
||||
expect(series.measureLowerBoundFn(0), equals(18 / 60));
|
||||
expect(series.measureLowerBoundFn(1), equals(28 / 90));
|
||||
expect(series.measureLowerBoundFn(2), equals(38 / 120));
|
||||
|
||||
expect(series.rawMeasureLowerBoundFn(0), equals(18));
|
||||
expect(series.rawMeasureLowerBoundFn(1), equals(28));
|
||||
expect(series.rawMeasureLowerBoundFn(2), equals(38));
|
||||
|
||||
expect(series.measureUpperBoundFn(0), equals(22 / 60));
|
||||
expect(series.measureUpperBoundFn(1), equals(32 / 90));
|
||||
expect(series.measureUpperBoundFn(2), equals(42 / 120));
|
||||
|
||||
expect(series.rawMeasureUpperBoundFn(0), equals(22));
|
||||
expect(series.rawMeasureUpperBoundFn(1), equals(32));
|
||||
expect(series.rawMeasureUpperBoundFn(2), equals(42));
|
||||
|
||||
// Verify sixth series.
|
||||
series = seriesList[5];
|
||||
|
||||
expect(series.measureFn(0), equals(30 / 60));
|
||||
expect(series.measureFn(1), equals(40 / 90));
|
||||
expect(series.measureFn(2), equals(50 / 120));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(30));
|
||||
expect(series.rawMeasureFn(1), equals(40));
|
||||
expect(series.rawMeasureFn(2), equals(50));
|
||||
|
||||
expect(series.measureLowerBoundFn(0), equals(28 / 60));
|
||||
expect(series.measureLowerBoundFn(1), equals(38 / 90));
|
||||
expect(series.measureLowerBoundFn(2), equals(48 / 120));
|
||||
|
||||
expect(series.rawMeasureLowerBoundFn(0), equals(28));
|
||||
expect(series.rawMeasureLowerBoundFn(1), equals(38));
|
||||
expect(series.rawMeasureLowerBoundFn(2), equals(48));
|
||||
|
||||
expect(series.measureUpperBoundFn(0), equals(32 / 60));
|
||||
expect(series.measureUpperBoundFn(1), equals(42 / 90));
|
||||
expect(series.measureUpperBoundFn(2), equals(52 / 120));
|
||||
|
||||
expect(series.rawMeasureUpperBoundFn(0), equals(32));
|
||||
expect(series.rawMeasureUpperBoundFn(1), equals(42));
|
||||
expect(series.rawMeasureUpperBoundFn(2), equals(52));
|
||||
});
|
||||
|
||||
test('percent of series', () {
|
||||
// Setup behavior.
|
||||
_makeBehavior(totalType: PercentInjectorTotalType.series);
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onData(seriesList);
|
||||
_chart.lastLifecycleListener.onPreprocess(seriesList);
|
||||
|
||||
// Verify that every series has a total measure value. Technically this is
|
||||
// handled in MutableSeries, but it is a pre-condition for this behavior
|
||||
// functioning properly.
|
||||
expect(seriesList[0].seriesMeasureTotal, equals(6));
|
||||
expect(seriesList[1].seriesMeasureTotal, equals(9));
|
||||
expect(seriesList[2].seriesMeasureTotal, equals(12));
|
||||
expect(seriesList[3].seriesMeasureTotal, equals(60));
|
||||
expect(seriesList[4].seriesMeasureTotal, equals(90));
|
||||
expect(seriesList[5].seriesMeasureTotal, equals(120));
|
||||
|
||||
// Verify first series.
|
||||
var series = seriesList[0];
|
||||
|
||||
expect(series.measureFn(0), equals(1 / 6));
|
||||
expect(series.measureFn(1), equals(2 / 6));
|
||||
expect(series.measureFn(2), equals(3 / 6));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(1));
|
||||
expect(series.rawMeasureFn(1), equals(2));
|
||||
expect(series.rawMeasureFn(2), equals(3));
|
||||
|
||||
// Verify second series.
|
||||
series = seriesList[1];
|
||||
|
||||
expect(series.measureFn(0), equals(2 / 9));
|
||||
expect(series.measureFn(1), equals(3 / 9));
|
||||
expect(series.measureFn(2), equals(4 / 9));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(2));
|
||||
expect(series.rawMeasureFn(1), equals(3));
|
||||
expect(series.rawMeasureFn(2), equals(4));
|
||||
|
||||
// Verify third series.
|
||||
series = seriesList[2];
|
||||
|
||||
expect(series.measureFn(0), equals(3 / 12));
|
||||
expect(series.measureFn(1), equals(4 / 12));
|
||||
expect(series.measureFn(2), equals(5 / 12));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(3));
|
||||
expect(series.rawMeasureFn(1), equals(4));
|
||||
expect(series.rawMeasureFn(2), equals(5));
|
||||
|
||||
// Verify fourth series.
|
||||
series = seriesList[3];
|
||||
|
||||
expect(series.measureFn(0), equals(10 / 60));
|
||||
expect(series.measureFn(1), equals(20 / 60));
|
||||
expect(series.measureFn(2), equals(30 / 60));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(10));
|
||||
expect(series.rawMeasureFn(1), equals(20));
|
||||
expect(series.rawMeasureFn(2), equals(30));
|
||||
|
||||
expect(series.measureLowerBoundFn(0), equals(8 / 60));
|
||||
expect(series.measureLowerBoundFn(1), equals(18 / 60));
|
||||
expect(series.measureLowerBoundFn(2), equals(28 / 60));
|
||||
|
||||
expect(series.rawMeasureLowerBoundFn(0), equals(8));
|
||||
expect(series.rawMeasureLowerBoundFn(1), equals(18));
|
||||
expect(series.rawMeasureLowerBoundFn(2), equals(28));
|
||||
|
||||
expect(series.measureUpperBoundFn(0), equals(12 / 60));
|
||||
expect(series.measureUpperBoundFn(1), equals(22 / 60));
|
||||
expect(series.measureUpperBoundFn(2), equals(32 / 60));
|
||||
|
||||
expect(series.rawMeasureUpperBoundFn(0), equals(12));
|
||||
expect(series.rawMeasureUpperBoundFn(1), equals(22));
|
||||
expect(series.rawMeasureUpperBoundFn(2), equals(32));
|
||||
|
||||
// Verify fifth series.
|
||||
series = seriesList[4];
|
||||
|
||||
expect(series.measureFn(0), equals(20 / 90));
|
||||
expect(series.measureFn(1), equals(30 / 90));
|
||||
expect(series.measureFn(2), equals(40 / 90));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(20));
|
||||
expect(series.rawMeasureFn(1), equals(30));
|
||||
expect(series.rawMeasureFn(2), equals(40));
|
||||
|
||||
expect(series.measureLowerBoundFn(0), equals(18 / 90));
|
||||
expect(series.measureLowerBoundFn(1), equals(28 / 90));
|
||||
expect(series.measureLowerBoundFn(2), equals(38 / 90));
|
||||
|
||||
expect(series.rawMeasureLowerBoundFn(0), equals(18));
|
||||
expect(series.rawMeasureLowerBoundFn(1), equals(28));
|
||||
expect(series.rawMeasureLowerBoundFn(2), equals(38));
|
||||
|
||||
expect(series.measureUpperBoundFn(0), equals(22 / 90));
|
||||
expect(series.measureUpperBoundFn(1), equals(32 / 90));
|
||||
expect(series.measureUpperBoundFn(2), equals(42 / 90));
|
||||
|
||||
expect(series.rawMeasureUpperBoundFn(0), equals(22));
|
||||
expect(series.rawMeasureUpperBoundFn(1), equals(32));
|
||||
expect(series.rawMeasureUpperBoundFn(2), equals(42));
|
||||
|
||||
// Verify sixth series.
|
||||
series = seriesList[5];
|
||||
|
||||
expect(series.measureFn(0), equals(30 / 120));
|
||||
expect(series.measureFn(1), equals(40 / 120));
|
||||
expect(series.measureFn(2), equals(50 / 120));
|
||||
|
||||
expect(series.rawMeasureFn(0), equals(30));
|
||||
expect(series.rawMeasureFn(1), equals(40));
|
||||
expect(series.rawMeasureFn(2), equals(50));
|
||||
|
||||
expect(series.measureLowerBoundFn(0), equals(28 / 120));
|
||||
expect(series.measureLowerBoundFn(1), equals(38 / 120));
|
||||
expect(series.measureLowerBoundFn(2), equals(48 / 120));
|
||||
|
||||
expect(series.rawMeasureLowerBoundFn(0), equals(28));
|
||||
expect(series.rawMeasureLowerBoundFn(1), equals(38));
|
||||
expect(series.rawMeasureLowerBoundFn(2), equals(48));
|
||||
|
||||
expect(series.measureUpperBoundFn(0), equals(32 / 120));
|
||||
expect(series.measureUpperBoundFn(1), equals(42 / 120));
|
||||
expect(series.measureUpperBoundFn(2), equals(52 / 120));
|
||||
|
||||
expect(series.rawMeasureUpperBoundFn(0), equals(32));
|
||||
expect(series.rawMeasureUpperBoundFn(1), equals(42));
|
||||
expect(series.rawMeasureUpperBoundFn(2), equals(52));
|
||||
});
|
||||
});
|
||||
|
||||
group('Life cycle', () {
|
||||
test('sets injected flag for percent of domain', () {
|
||||
// Setup behavior.
|
||||
_makeBehavior(totalType: PercentInjectorTotalType.domain);
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onData(seriesList);
|
||||
|
||||
// Verify that each series has an initially false flag.
|
||||
expect(seriesList[0].getAttr(percentInjectedKey), isFalse);
|
||||
expect(seriesList[1].getAttr(percentInjectedKey), isFalse);
|
||||
expect(seriesList[2].getAttr(percentInjectedKey), isFalse);
|
||||
expect(seriesList[3].getAttr(percentInjectedKey), isFalse);
|
||||
expect(seriesList[4].getAttr(percentInjectedKey), isFalse);
|
||||
expect(seriesList[5].getAttr(percentInjectedKey), isFalse);
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onPreprocess(seriesList);
|
||||
|
||||
// Verify that each series has a true flag.
|
||||
expect(seriesList[0].getAttr(percentInjectedKey), isTrue);
|
||||
expect(seriesList[1].getAttr(percentInjectedKey), isTrue);
|
||||
expect(seriesList[2].getAttr(percentInjectedKey), isTrue);
|
||||
expect(seriesList[3].getAttr(percentInjectedKey), isTrue);
|
||||
expect(seriesList[4].getAttr(percentInjectedKey), isTrue);
|
||||
expect(seriesList[5].getAttr(percentInjectedKey), isTrue);
|
||||
});
|
||||
|
||||
test('sets injected flag for percent of series', () {
|
||||
// Setup behavior.
|
||||
_makeBehavior(totalType: PercentInjectorTotalType.series);
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onData(seriesList);
|
||||
|
||||
// Verify that each series has an initially false flag.
|
||||
expect(seriesList[0].getAttr(percentInjectedKey), isFalse);
|
||||
expect(seriesList[1].getAttr(percentInjectedKey), isFalse);
|
||||
expect(seriesList[2].getAttr(percentInjectedKey), isFalse);
|
||||
expect(seriesList[3].getAttr(percentInjectedKey), isFalse);
|
||||
expect(seriesList[4].getAttr(percentInjectedKey), isFalse);
|
||||
expect(seriesList[5].getAttr(percentInjectedKey), isFalse);
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onPreprocess(seriesList);
|
||||
|
||||
// Verify that each series has a true flag.
|
||||
expect(seriesList[0].getAttr(percentInjectedKey), isTrue);
|
||||
expect(seriesList[1].getAttr(percentInjectedKey), isTrue);
|
||||
expect(seriesList[2].getAttr(percentInjectedKey), isTrue);
|
||||
expect(seriesList[3].getAttr(percentInjectedKey), isTrue);
|
||||
expect(seriesList[4].getAttr(percentInjectedKey), isTrue);
|
||||
expect(seriesList[5].getAttr(percentInjectedKey), isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'package:charts_common/src/chart/common/series_renderer.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'package:charts_common/src/chart/common/base_chart.dart';
|
||||
import 'package:charts_common/src/chart/common/behavior/chart_behavior.dart';
|
||||
import 'package:charts_common/src/chart/common/datum_details.dart';
|
||||
import 'package:charts_common/src/chart/common/selection_model/selection_model.dart';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockBehavior extends Mock implements ChartBehavior<String> {}
|
||||
|
||||
class ParentBehavior implements ChartBehavior<String> {
|
||||
final ChartBehavior<String> child;
|
||||
|
||||
ParentBehavior(this.child);
|
||||
|
||||
String get role => null;
|
||||
|
||||
@override
|
||||
void attachTo(BaseChart chart) {
|
||||
chart.addBehavior(child);
|
||||
}
|
||||
|
||||
@override
|
||||
void removeFrom(BaseChart chart) {
|
||||
chart.removeBehavior(child);
|
||||
}
|
||||
}
|
||||
|
||||
class ConcreteChart extends BaseChart<String> {
|
||||
@override
|
||||
SeriesRenderer<String> makeDefaultRenderer() => null;
|
||||
|
||||
@override
|
||||
List<DatumDetails<String>> getDatumDetails(SelectionModelType _) => null;
|
||||
}
|
||||
|
||||
void main() {
|
||||
ConcreteChart chart;
|
||||
MockBehavior namedBehavior;
|
||||
MockBehavior unnamedBehavior;
|
||||
|
||||
setUp(() {
|
||||
chart = new ConcreteChart();
|
||||
|
||||
namedBehavior = new MockBehavior();
|
||||
when(namedBehavior.role).thenReturn('foo');
|
||||
|
||||
unnamedBehavior = new MockBehavior();
|
||||
when(unnamedBehavior.role).thenReturn(null);
|
||||
});
|
||||
|
||||
group('Attach & Detach', () {
|
||||
test('attach is called once', () {
|
||||
chart.addBehavior(namedBehavior);
|
||||
verify(namedBehavior.attachTo(chart)).called(1);
|
||||
|
||||
verify(namedBehavior.role);
|
||||
verifyNoMoreInteractions(namedBehavior);
|
||||
});
|
||||
|
||||
test('deteach is called once', () {
|
||||
chart.addBehavior(namedBehavior);
|
||||
verify(namedBehavior.attachTo(chart)).called(1);
|
||||
|
||||
chart.removeBehavior(namedBehavior);
|
||||
verify(namedBehavior.removeFrom(chart)).called(1);
|
||||
|
||||
verify(namedBehavior.role);
|
||||
verifyNoMoreInteractions(namedBehavior);
|
||||
});
|
||||
|
||||
test('detach is called when name is reused', () {
|
||||
final otherBehavior = new MockBehavior();
|
||||
when(otherBehavior.role).thenReturn('foo');
|
||||
|
||||
chart.addBehavior(namedBehavior);
|
||||
verify(namedBehavior.attachTo(chart)).called(1);
|
||||
|
||||
chart.addBehavior(otherBehavior);
|
||||
verify(namedBehavior.removeFrom(chart)).called(1);
|
||||
verify(otherBehavior.attachTo(chart)).called(1);
|
||||
|
||||
verify(namedBehavior.role);
|
||||
verify(otherBehavior.role);
|
||||
verifyNoMoreInteractions(namedBehavior);
|
||||
verifyNoMoreInteractions(otherBehavior);
|
||||
});
|
||||
|
||||
test('detach is not called when name is null', () {
|
||||
chart.addBehavior(namedBehavior);
|
||||
verify(namedBehavior.attachTo(chart)).called(1);
|
||||
|
||||
chart.addBehavior(unnamedBehavior);
|
||||
verify(unnamedBehavior.attachTo(chart)).called(1);
|
||||
|
||||
verify(namedBehavior.role);
|
||||
verify(unnamedBehavior.role);
|
||||
verifyNoMoreInteractions(namedBehavior);
|
||||
verifyNoMoreInteractions(unnamedBehavior);
|
||||
});
|
||||
|
||||
test('detach is not called when name is different', () {
|
||||
final otherBehavior = new MockBehavior();
|
||||
when(otherBehavior.role).thenReturn('bar');
|
||||
|
||||
chart.addBehavior(namedBehavior);
|
||||
verify(namedBehavior.attachTo(chart)).called(1);
|
||||
|
||||
chart.addBehavior(otherBehavior);
|
||||
verify(otherBehavior.attachTo(chart)).called(1);
|
||||
|
||||
verify(namedBehavior.role);
|
||||
verify(otherBehavior.role);
|
||||
verifyNoMoreInteractions(namedBehavior);
|
||||
verifyNoMoreInteractions(otherBehavior);
|
||||
});
|
||||
|
||||
test('behaviors are removed when chart is destroyed', () {
|
||||
final parentBehavior = new ParentBehavior(unnamedBehavior);
|
||||
|
||||
chart.addBehavior(parentBehavior);
|
||||
// The parent should add the child behavoir.
|
||||
verify(unnamedBehavior.attachTo(chart)).called(1);
|
||||
|
||||
chart.destroy();
|
||||
|
||||
// The parent should remove the child behavior.
|
||||
verify(unnamedBehavior.removeFrom(chart)).called(1);
|
||||
|
||||
// Remove should only be called once and shouldn't trigger a concurrent
|
||||
// modification exception.
|
||||
verify(unnamedBehavior.role);
|
||||
verifyNoMoreInteractions(unnamedBehavior);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'package:charts_common/src/chart/common/base_chart.dart';
|
||||
import 'package:charts_common/src/chart/common/behavior/domain_highlighter.dart';
|
||||
import 'package:charts_common/src/chart/common/processed_series.dart';
|
||||
import 'package:charts_common/src/chart/common/selection_model/selection_model.dart';
|
||||
import 'package:charts_common/src/common/material_palette.dart';
|
||||
import 'package:charts_common/src/data/series.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockChart extends Mock implements BaseChart {
|
||||
LifecycleListener lastListener;
|
||||
|
||||
@override
|
||||
addLifecycleListener(LifecycleListener listener) => lastListener = listener;
|
||||
|
||||
@override
|
||||
removeLifecycleListener(LifecycleListener listener) {
|
||||
expect(listener, equals(lastListener));
|
||||
lastListener = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class MockSelectionModel extends Mock implements MutableSelectionModel {
|
||||
SelectionModelListener lastListener;
|
||||
|
||||
@override
|
||||
addSelectionChangedListener(SelectionModelListener listener) =>
|
||||
lastListener = listener;
|
||||
|
||||
@override
|
||||
removeSelectionChangedListener(SelectionModelListener listener) {
|
||||
expect(listener, equals(lastListener));
|
||||
lastListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
MockChart _chart;
|
||||
MockSelectionModel _selectionModel;
|
||||
|
||||
MutableSeries<String> _series1;
|
||||
final _s1D1 = new MyRow('s1d1', 11);
|
||||
final _s1D2 = new MyRow('s1d2', 12);
|
||||
final _s1D3 = new MyRow('s1d3', 13);
|
||||
|
||||
MutableSeries<String> _series2;
|
||||
final _s2D1 = new MyRow('s2d1', 21);
|
||||
final _s2D2 = new MyRow('s2d2', 22);
|
||||
final _s2D3 = new MyRow('s2d3', 23);
|
||||
|
||||
_setupSelection(List<MyRow> selected) {
|
||||
for (var i = 0; i < _series1.data.length; i++) {
|
||||
when(_selectionModel.isDatumSelected(_series1, i))
|
||||
.thenReturn(selected.contains(_series1.data[i]));
|
||||
}
|
||||
for (var i = 0; i < _series2.data.length; i++) {
|
||||
when(_selectionModel.isDatumSelected(_series2, i))
|
||||
.thenReturn(selected.contains(_series2.data[i]));
|
||||
}
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
_chart = new MockChart();
|
||||
|
||||
_selectionModel = new MockSelectionModel();
|
||||
when(_chart.getSelectionModel(SelectionModelType.info))
|
||||
.thenReturn(_selectionModel);
|
||||
|
||||
_series1 = new MutableSeries(new Series<MyRow, String>(
|
||||
id: 's1',
|
||||
data: [_s1D1, _s1D2, _s1D3],
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.count,
|
||||
colorFn: (_, __) => MaterialPalette.blue.shadeDefault))
|
||||
..measureFn = (_) => 0.0;
|
||||
|
||||
_series2 = new MutableSeries(new Series<MyRow, String>(
|
||||
id: 's2',
|
||||
data: [_s2D1, _s2D2, _s2D3],
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.count,
|
||||
colorFn: (_, __) => MaterialPalette.red.shadeDefault))
|
||||
..measureFn = (_) => 0.0;
|
||||
});
|
||||
|
||||
group('DomainHighligher', () {
|
||||
test('darkens the selected bars', () {
|
||||
// Setup
|
||||
final behavior = new DomainHighlighter(SelectionModelType.info);
|
||||
behavior.attachTo(_chart);
|
||||
_setupSelection([_s1D2, _s2D2]);
|
||||
final seriesList = [_series1, _series2];
|
||||
|
||||
// Act
|
||||
_selectionModel.lastListener(_selectionModel);
|
||||
verify(_chart.redraw(skipAnimation: true, skipLayout: true));
|
||||
_chart.lastListener.onPostprocess(seriesList);
|
||||
|
||||
// Verify
|
||||
final s1ColorFn = _series1.colorFn;
|
||||
expect(s1ColorFn(0), equals(MaterialPalette.blue.shadeDefault));
|
||||
expect(s1ColorFn(1), equals(MaterialPalette.blue.shadeDefault.darker));
|
||||
expect(s1ColorFn(2), equals(MaterialPalette.blue.shadeDefault));
|
||||
|
||||
final s2ColorFn = _series2.colorFn;
|
||||
expect(s2ColorFn(0), equals(MaterialPalette.red.shadeDefault));
|
||||
expect(s2ColorFn(1), equals(MaterialPalette.red.shadeDefault.darker));
|
||||
expect(s2ColorFn(2), equals(MaterialPalette.red.shadeDefault));
|
||||
});
|
||||
|
||||
test('listens to other selection models', () {
|
||||
// Setup
|
||||
final behavior = new DomainHighlighter(SelectionModelType.action);
|
||||
when(_chart.getSelectionModel(SelectionModelType.action))
|
||||
.thenReturn(_selectionModel);
|
||||
|
||||
// Act
|
||||
behavior.attachTo(_chart);
|
||||
|
||||
// Verify
|
||||
verify(_chart.getSelectionModel(SelectionModelType.action));
|
||||
verifyNever(_chart.getSelectionModel(SelectionModelType.info));
|
||||
});
|
||||
|
||||
test('leaves everything alone with no selection', () {
|
||||
// Setup
|
||||
final behavior = new DomainHighlighter(SelectionModelType.info);
|
||||
behavior.attachTo(_chart);
|
||||
_setupSelection([]);
|
||||
final seriesList = [_series1, _series2];
|
||||
|
||||
// Act
|
||||
_selectionModel.lastListener(_selectionModel);
|
||||
verify(_chart.redraw(skipAnimation: true, skipLayout: true));
|
||||
_chart.lastListener.onPostprocess(seriesList);
|
||||
|
||||
// Verify
|
||||
final s1ColorFn = _series1.colorFn;
|
||||
expect(s1ColorFn(0), equals(MaterialPalette.blue.shadeDefault));
|
||||
expect(s1ColorFn(1), equals(MaterialPalette.blue.shadeDefault));
|
||||
expect(s1ColorFn(2), equals(MaterialPalette.blue.shadeDefault));
|
||||
|
||||
final s2ColorFn = _series2.colorFn;
|
||||
expect(s2ColorFn(0), equals(MaterialPalette.red.shadeDefault));
|
||||
expect(s2ColorFn(1), equals(MaterialPalette.red.shadeDefault));
|
||||
expect(s2ColorFn(2), equals(MaterialPalette.red.shadeDefault));
|
||||
});
|
||||
|
||||
test('cleans up', () {
|
||||
// Setup
|
||||
final behavior = new DomainHighlighter(SelectionModelType.info);
|
||||
behavior.attachTo(_chart);
|
||||
_setupSelection([_s1D2, _s2D2]);
|
||||
|
||||
// Act
|
||||
behavior.removeFrom(_chart);
|
||||
|
||||
// Verify
|
||||
expect(_chart.lastListener, isNull);
|
||||
expect(_selectionModel.lastListener, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MyRow {
|
||||
final String campaign;
|
||||
final int count;
|
||||
MyRow(this.campaign, this.count);
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:charts_common/src/chart/common/base_chart.dart';
|
||||
import 'package:charts_common/src/chart/common/chart_canvas.dart';
|
||||
import 'package:charts_common/src/chart/common/datum_details.dart';
|
||||
import 'package:charts_common/src/chart/common/behavior/initial_selection.dart';
|
||||
import 'package:charts_common/src/chart/common/processed_series.dart';
|
||||
import 'package:charts_common/src/chart/common/series_datum.dart';
|
||||
import 'package:charts_common/src/chart/common/series_renderer.dart';
|
||||
import 'package:charts_common/src/chart/common/selection_model/selection_model.dart';
|
||||
import 'package:charts_common/src/data/series.dart';
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class FakeRenderer extends BaseSeriesRenderer {
|
||||
@override
|
||||
DatumDetails addPositionToDetailsForSeriesDatum(
|
||||
DatumDetails details, SeriesDatum seriesDatum) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
List<DatumDetails> getNearestDatumDetailPerSeries(
|
||||
Point<double> chartPoint, bool byDomain, Rectangle<int> boundsOverride) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(ChartCanvas canvas, double animationPercent) {}
|
||||
|
||||
@override
|
||||
void update(List<ImmutableSeries> seriesList, bool isAnimating) {}
|
||||
}
|
||||
|
||||
class FakeChart extends BaseChart {
|
||||
@override
|
||||
List<DatumDetails> getDatumDetails(SelectionModelType type) => [];
|
||||
|
||||
@override
|
||||
SeriesRenderer makeDefaultRenderer() => new FakeRenderer();
|
||||
|
||||
void requestOnDraw(List<MutableSeries> seriesList) {
|
||||
fireOnDraw(seriesList);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
FakeChart _chart;
|
||||
ImmutableSeries _series1;
|
||||
ImmutableSeries _series2;
|
||||
ImmutableSeries _series3;
|
||||
ImmutableSeries _series4;
|
||||
final infoSelectionType = SelectionModelType.info;
|
||||
|
||||
InitialSelection _makeBehavior(SelectionModelType selectionModelType,
|
||||
{List<String> selectedSeries, List<SeriesDatumConfig> selectedData}) {
|
||||
InitialSelection behavior = new InitialSelection(
|
||||
selectionModelType: selectionModelType,
|
||||
selectedSeriesConfig: selectedSeries,
|
||||
selectedDataConfig: selectedData);
|
||||
|
||||
behavior.attachTo(_chart);
|
||||
|
||||
return behavior;
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
_chart = new FakeChart();
|
||||
|
||||
_series1 = new MutableSeries(new Series(
|
||||
id: 'mySeries1',
|
||||
data: ['A', 'B', 'C', 'D'],
|
||||
domainFn: (dynamic datum, __) => datum,
|
||||
measureFn: (_, __) {}));
|
||||
|
||||
_series2 = new MutableSeries(new Series(
|
||||
id: 'mySeries2',
|
||||
data: ['W', 'X', 'Y', 'Z'],
|
||||
domainFn: (dynamic datum, __) => datum,
|
||||
measureFn: (_, __) {}));
|
||||
|
||||
_series3 = new MutableSeries(new Series(
|
||||
id: 'mySeries3',
|
||||
data: ['W', 'X', 'Y', 'Z'],
|
||||
domainFn: (dynamic datum, __) => datum,
|
||||
measureFn: (_, __) {}));
|
||||
|
||||
_series4 = new MutableSeries(new Series(
|
||||
id: 'mySeries4',
|
||||
data: ['W', 'X', 'Y', 'Z'],
|
||||
domainFn: (dynamic datum, __) => datum,
|
||||
measureFn: (_, __) {}));
|
||||
});
|
||||
|
||||
test('selects initial datum', () {
|
||||
_makeBehavior(infoSelectionType,
|
||||
selectedData: [new SeriesDatumConfig('mySeries1', 'C')]);
|
||||
|
||||
_chart.requestOnDraw([_series1, _series2]);
|
||||
|
||||
final model = _chart.getSelectionModel(infoSelectionType);
|
||||
|
||||
expect(model.selectedSeries, hasLength(1));
|
||||
expect(model.selectedSeries[0], equals(_series1));
|
||||
expect(model.selectedDatum, hasLength(1));
|
||||
expect(model.selectedDatum[0].series, equals(_series1));
|
||||
expect(model.selectedDatum[0].datum, equals('C'));
|
||||
});
|
||||
|
||||
test('selects multiple initial data', () {
|
||||
_makeBehavior(infoSelectionType, selectedData: [
|
||||
new SeriesDatumConfig('mySeries1', 'C'),
|
||||
new SeriesDatumConfig('mySeries1', 'D')
|
||||
]);
|
||||
|
||||
_chart.requestOnDraw([_series1, _series2]);
|
||||
|
||||
final model = _chart.getSelectionModel(infoSelectionType);
|
||||
|
||||
expect(model.selectedSeries, hasLength(1));
|
||||
expect(model.selectedSeries[0], equals(_series1));
|
||||
expect(model.selectedDatum, hasLength(2));
|
||||
expect(model.selectedDatum[0].series, equals(_series1));
|
||||
expect(model.selectedDatum[0].datum, equals('C'));
|
||||
expect(model.selectedDatum[1].series, equals(_series1));
|
||||
expect(model.selectedDatum[1].datum, equals('D'));
|
||||
});
|
||||
|
||||
test('selects initial series', () {
|
||||
_makeBehavior(infoSelectionType, selectedSeries: ['mySeries2']);
|
||||
|
||||
_chart.requestOnDraw([_series1, _series2, _series3, _series4]);
|
||||
|
||||
final model = _chart.getSelectionModel(infoSelectionType);
|
||||
|
||||
expect(model.selectedSeries, hasLength(1));
|
||||
expect(model.selectedSeries[0], equals(_series2));
|
||||
expect(model.selectedDatum, isEmpty);
|
||||
});
|
||||
|
||||
test('selects multiple series', () {
|
||||
_makeBehavior(infoSelectionType,
|
||||
selectedSeries: ['mySeries2', 'mySeries4']);
|
||||
|
||||
_chart.requestOnDraw([_series1, _series2, _series3, _series4]);
|
||||
|
||||
final model = _chart.getSelectionModel(infoSelectionType);
|
||||
|
||||
expect(model.selectedSeries, hasLength(2));
|
||||
expect(model.selectedSeries[0], equals(_series2));
|
||||
expect(model.selectedSeries[1], equals(_series4));
|
||||
expect(model.selectedDatum, isEmpty);
|
||||
});
|
||||
|
||||
test('selects series and datum', () {
|
||||
_makeBehavior(infoSelectionType,
|
||||
selectedData: [new SeriesDatumConfig('mySeries1', 'C')],
|
||||
selectedSeries: ['mySeries4']);
|
||||
|
||||
_chart.requestOnDraw([_series1, _series2, _series3, _series4]);
|
||||
|
||||
final model = _chart.getSelectionModel(infoSelectionType);
|
||||
|
||||
expect(model.selectedSeries, hasLength(2));
|
||||
expect(model.selectedSeries[0], equals(_series1));
|
||||
expect(model.selectedSeries[1], equals(_series4));
|
||||
expect(model.selectedDatum[0].series, equals(_series1));
|
||||
expect(model.selectedDatum[0].datum, equals('C'));
|
||||
});
|
||||
|
||||
test('selection model is reset when a new series is drawn', () {
|
||||
_makeBehavior(infoSelectionType, selectedSeries: ['mySeries2']);
|
||||
|
||||
_chart.requestOnDraw([_series1, _series2, _series3, _series4]);
|
||||
|
||||
final model = _chart.getSelectionModel(infoSelectionType);
|
||||
|
||||
// Verify initial selection is selected on first draw
|
||||
expect(model.selectedSeries, hasLength(1));
|
||||
expect(model.selectedSeries[0], equals(_series2));
|
||||
expect(model.selectedDatum, isEmpty);
|
||||
|
||||
// Request a draw with a new series list.
|
||||
_chart.draw(
|
||||
[
|
||||
new Series(
|
||||
id: 'mySeries2',
|
||||
data: ['W', 'X', 'Y', 'Z'],
|
||||
domainFn: (dynamic datum, __) => datum,
|
||||
measureFn: (_, __) {})
|
||||
],
|
||||
);
|
||||
|
||||
// Verify selection is cleared.
|
||||
expect(model.selectedSeries, isEmpty);
|
||||
expect(model.selectedDatum, isEmpty);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'dart:math' show Point, Rectangle;
|
||||
|
||||
import 'package:charts_common/src/chart/cartesian/cartesian_chart.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/axis.dart';
|
||||
import 'package:charts_common/src/chart/common/base_chart.dart';
|
||||
import 'package:charts_common/src/chart/common/behavior/line_point_highlighter.dart';
|
||||
import 'package:charts_common/src/chart/common/datum_details.dart';
|
||||
import 'package:charts_common/src/chart/common/processed_series.dart';
|
||||
import 'package:charts_common/src/chart/common/series_datum.dart';
|
||||
import 'package:charts_common/src/chart/common/series_renderer.dart';
|
||||
import 'package:charts_common/src/chart/common/selection_model/selection_model.dart';
|
||||
import 'package:charts_common/src/common/material_palette.dart';
|
||||
import 'package:charts_common/src/data/series.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockChart extends Mock implements CartesianChart {
|
||||
LifecycleListener lastListener;
|
||||
|
||||
@override
|
||||
addLifecycleListener(LifecycleListener listener) => lastListener = listener;
|
||||
|
||||
@override
|
||||
removeLifecycleListener(LifecycleListener listener) {
|
||||
expect(listener, equals(lastListener));
|
||||
lastListener = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get vertical => true;
|
||||
}
|
||||
|
||||
class MockSelectionModel extends Mock implements MutableSelectionModel {
|
||||
SelectionModelListener lastListener;
|
||||
|
||||
@override
|
||||
addSelectionChangedListener(SelectionModelListener listener) =>
|
||||
lastListener = listener;
|
||||
|
||||
@override
|
||||
removeSelectionChangedListener(SelectionModelListener listener) {
|
||||
expect(listener, equals(lastListener));
|
||||
lastListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
class MockNumericAxis extends Mock implements NumericAxis {
|
||||
@override
|
||||
getLocation(num domain) {
|
||||
return 10.0;
|
||||
}
|
||||
}
|
||||
|
||||
class MockSeriesRenderer extends BaseSeriesRenderer {
|
||||
@override
|
||||
void update(_, __) {}
|
||||
|
||||
@override
|
||||
void paint(_, __) {}
|
||||
|
||||
List<DatumDetails> getNearestDatumDetailPerSeries(
|
||||
Point<double> chartPoint, bool byDomain, Rectangle<int> boundsOverride) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DatumDetails addPositionToDetailsForSeriesDatum(
|
||||
DatumDetails details, SeriesDatum seriesDatum) {
|
||||
return new DatumDetails.from(details,
|
||||
chartPosition: new Point<double>(0.0, 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
MockChart _chart;
|
||||
MockSelectionModel _selectionModel;
|
||||
MockSeriesRenderer _seriesRenderer;
|
||||
|
||||
MutableSeries<int> _series1;
|
||||
final _s1D1 = new MyRow(1, 11);
|
||||
final _s1D2 = new MyRow(2, 12);
|
||||
final _s1D3 = new MyRow(3, 13);
|
||||
|
||||
MutableSeries<int> _series2;
|
||||
final _s2D1 = new MyRow(4, 21);
|
||||
final _s2D2 = new MyRow(5, 22);
|
||||
final _s2D3 = new MyRow(6, 23);
|
||||
|
||||
List<DatumDetails> _mockGetSelectedDatumDetails(List<SeriesDatum> selection) {
|
||||
final details = <DatumDetails>[];
|
||||
|
||||
for (SeriesDatum seriesDatum in selection) {
|
||||
details.add(_seriesRenderer.getDetailsForSeriesDatum(seriesDatum));
|
||||
}
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
_setupSelection(List<SeriesDatum> selection) {
|
||||
final selected = <MyRow>[];
|
||||
|
||||
for (var i = 0; i < selection.length; i++) {
|
||||
selected.add(selection[0].datum);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _series1.data.length; i++) {
|
||||
when(_selectionModel.isDatumSelected(_series1, i))
|
||||
.thenReturn(selected.contains(_series1.data[i]));
|
||||
}
|
||||
for (int i = 0; i < _series2.data.length; i++) {
|
||||
when(_selectionModel.isDatumSelected(_series2, i))
|
||||
.thenReturn(selected.contains(_series2.data[i]));
|
||||
}
|
||||
|
||||
when(_selectionModel.selectedDatum).thenReturn(selection);
|
||||
|
||||
final selectedDetails = _mockGetSelectedDatumDetails(selection);
|
||||
|
||||
when(_chart.getSelectedDatumDetails(SelectionModelType.info))
|
||||
.thenReturn(selectedDetails);
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
_chart = new MockChart();
|
||||
|
||||
_seriesRenderer = new MockSeriesRenderer();
|
||||
|
||||
_selectionModel = new MockSelectionModel();
|
||||
when(_chart.getSelectionModel(SelectionModelType.info))
|
||||
.thenReturn(_selectionModel);
|
||||
|
||||
_series1 = new MutableSeries(new Series<MyRow, int>(
|
||||
id: 's1',
|
||||
data: [_s1D1, _s1D2, _s1D3],
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.count,
|
||||
colorFn: (_, __) => MaterialPalette.blue.shadeDefault))
|
||||
..measureFn = (_) => 0.0;
|
||||
|
||||
_series2 = new MutableSeries(new Series<MyRow, int>(
|
||||
id: 's2',
|
||||
data: [_s2D1, _s2D2, _s2D3],
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.count,
|
||||
colorFn: (_, __) => MaterialPalette.red.shadeDefault))
|
||||
..measureFn = (_) => 0.0;
|
||||
});
|
||||
|
||||
group('LinePointHighlighter', () {
|
||||
test('highlights the selected points', () {
|
||||
// Setup
|
||||
final behavior =
|
||||
new LinePointHighlighter(selectionModelType: SelectionModelType.info);
|
||||
final tester = new LinePointHighlighterTester(behavior);
|
||||
behavior.attachTo(_chart);
|
||||
_setupSelection([
|
||||
new SeriesDatum(_series1, _s1D2),
|
||||
new SeriesDatum(_series2, _s2D2),
|
||||
]);
|
||||
|
||||
// Mock axes for returning fake domain locations.
|
||||
Axis domainAxis = new MockNumericAxis();
|
||||
Axis primaryMeasureAxis = new MockNumericAxis();
|
||||
|
||||
_series1.setAttr(domainAxisKey, domainAxis);
|
||||
_series1.setAttr(measureAxisKey, primaryMeasureAxis);
|
||||
_series1.measureOffsetFn = (_) => 0.0;
|
||||
|
||||
_series2.setAttr(domainAxisKey, domainAxis);
|
||||
_series2.setAttr(measureAxisKey, primaryMeasureAxis);
|
||||
_series2.measureOffsetFn = (_) => 0.0;
|
||||
|
||||
// Act
|
||||
_selectionModel.lastListener(_selectionModel);
|
||||
verify(_chart.redraw(skipAnimation: true, skipLayout: true));
|
||||
|
||||
_chart.lastListener.onAxisConfigured();
|
||||
|
||||
// Verify
|
||||
expect(tester.getSelectionLength(), equals(2));
|
||||
|
||||
expect(tester.isDatumSelected(_series1.data[0]), equals(false));
|
||||
expect(tester.isDatumSelected(_series1.data[1]), equals(true));
|
||||
expect(tester.isDatumSelected(_series1.data[2]), equals(false));
|
||||
|
||||
expect(tester.isDatumSelected(_series2.data[0]), equals(false));
|
||||
expect(tester.isDatumSelected(_series2.data[1]), equals(true));
|
||||
expect(tester.isDatumSelected(_series2.data[2]), equals(false));
|
||||
});
|
||||
|
||||
test('listens to other selection models', () {
|
||||
// Setup
|
||||
final behavior = new LinePointHighlighter(
|
||||
selectionModelType: SelectionModelType.action);
|
||||
when(_chart.getSelectionModel(SelectionModelType.action))
|
||||
.thenReturn(_selectionModel);
|
||||
|
||||
// Act
|
||||
behavior.attachTo(_chart);
|
||||
|
||||
// Verify
|
||||
verify(_chart.getSelectionModel(SelectionModelType.action));
|
||||
verifyNever(_chart.getSelectionModel(SelectionModelType.info));
|
||||
});
|
||||
|
||||
test('leaves everything alone with no selection', () {
|
||||
// Setup
|
||||
final behavior =
|
||||
new LinePointHighlighter(selectionModelType: SelectionModelType.info);
|
||||
final tester = new LinePointHighlighterTester(behavior);
|
||||
behavior.attachTo(_chart);
|
||||
_setupSelection([]);
|
||||
|
||||
// Act
|
||||
_selectionModel.lastListener(_selectionModel);
|
||||
verify(_chart.redraw(skipAnimation: true, skipLayout: true));
|
||||
_chart.lastListener.onAxisConfigured();
|
||||
|
||||
// Verify
|
||||
expect(tester.getSelectionLength(), equals(0));
|
||||
|
||||
expect(tester.isDatumSelected(_series1.data[0]), equals(false));
|
||||
expect(tester.isDatumSelected(_series1.data[1]), equals(false));
|
||||
expect(tester.isDatumSelected(_series1.data[2]), equals(false));
|
||||
|
||||
expect(tester.isDatumSelected(_series2.data[0]), equals(false));
|
||||
expect(tester.isDatumSelected(_series2.data[1]), equals(false));
|
||||
expect(tester.isDatumSelected(_series2.data[2]), equals(false));
|
||||
});
|
||||
|
||||
test('cleans up', () {
|
||||
// Setup
|
||||
final behavior =
|
||||
new LinePointHighlighter(selectionModelType: SelectionModelType.info);
|
||||
behavior.attachTo(_chart);
|
||||
_setupSelection([
|
||||
new SeriesDatum(_series1, _s1D2),
|
||||
new SeriesDatum(_series2, _s2D2),
|
||||
]);
|
||||
|
||||
// Act
|
||||
behavior.removeFrom(_chart);
|
||||
|
||||
// Verify
|
||||
expect(_chart.lastListener, isNull);
|
||||
expect(_selectionModel.lastListener, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MyRow {
|
||||
final int campaign;
|
||||
final int count;
|
||||
MyRow(this.campaign, this.count);
|
||||
}
|
||||
@@ -0,0 +1,351 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'dart:math' show Rectangle;
|
||||
|
||||
import 'package:charts_common/src/chart/cartesian/axis/axis.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/numeric_tick_provider.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/tick_formatter.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/linear/linear_scale.dart';
|
||||
import 'package:charts_common/src/chart/common/base_chart.dart';
|
||||
import 'package:charts_common/src/chart/common/chart_context.dart';
|
||||
import 'package:charts_common/src/chart/common/behavior/range_annotation.dart';
|
||||
import 'package:charts_common/src/chart/line/line_chart.dart';
|
||||
import 'package:charts_common/src/common/material_palette.dart';
|
||||
import 'package:charts_common/src/data/series.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockContext extends Mock implements ChartContext {}
|
||||
|
||||
class ConcreteChart extends LineChart {
|
||||
LifecycleListener<num> lastListener;
|
||||
|
||||
final _domainAxis = new ConcreteNumericAxis();
|
||||
|
||||
final _primaryMeasureAxis = new ConcreteNumericAxis();
|
||||
|
||||
@override
|
||||
addLifecycleListener(LifecycleListener listener) {
|
||||
lastListener = listener;
|
||||
return super.addLifecycleListener(listener);
|
||||
}
|
||||
|
||||
@override
|
||||
removeLifecycleListener(LifecycleListener listener) {
|
||||
expect(listener, equals(lastListener));
|
||||
lastListener = null;
|
||||
return super.removeLifecycleListener(listener);
|
||||
}
|
||||
|
||||
@override
|
||||
Axis get domainAxis => _domainAxis;
|
||||
|
||||
@override
|
||||
Axis getMeasureAxis({String axisId}) => _primaryMeasureAxis;
|
||||
}
|
||||
|
||||
class ConcreteNumericAxis extends Axis<num> {
|
||||
ConcreteNumericAxis()
|
||||
: super(
|
||||
tickProvider: new MockTickProvider(),
|
||||
tickFormatter: new NumericTickFormatter(),
|
||||
scale: new LinearScale(),
|
||||
);
|
||||
}
|
||||
|
||||
class MockTickProvider extends Mock implements NumericTickProvider {}
|
||||
|
||||
void main() {
|
||||
Rectangle<int> drawBounds;
|
||||
Rectangle<int> domainAxisBounds;
|
||||
Rectangle<int> measureAxisBounds;
|
||||
|
||||
ConcreteChart _chart;
|
||||
|
||||
Series<MyRow, int> _series1;
|
||||
final _s1D1 = new MyRow(0, 11);
|
||||
final _s1D2 = new MyRow(1, 12);
|
||||
final _s1D3 = new MyRow(2, 13);
|
||||
|
||||
Series<MyRow, int> _series2;
|
||||
final _s2D1 = new MyRow(3, 21);
|
||||
final _s2D2 = new MyRow(4, 22);
|
||||
final _s2D3 = new MyRow(5, 23);
|
||||
|
||||
const _dashPattern = const <int>[2, 3];
|
||||
|
||||
List<RangeAnnotationSegment<num>> _annotations1;
|
||||
|
||||
List<RangeAnnotationSegment<num>> _annotations2;
|
||||
|
||||
List<LineAnnotationSegment<num>> _annotations3;
|
||||
|
||||
ConcreteChart _makeChart() {
|
||||
final chart = new ConcreteChart();
|
||||
|
||||
final context = new MockContext();
|
||||
when(context.chartContainerIsRtl).thenReturn(false);
|
||||
when(context.isRtl).thenReturn(false);
|
||||
chart.context = context;
|
||||
|
||||
return chart;
|
||||
}
|
||||
|
||||
/// Initializes the [chart], draws the [seriesList], and configures mock axis
|
||||
/// layout bounds.
|
||||
_drawSeriesList(ConcreteChart chart, List<Series<MyRow, int>> seriesList) {
|
||||
_chart.domainAxis.autoViewport = true;
|
||||
_chart.domainAxis.resetDomains();
|
||||
|
||||
_chart.getMeasureAxis().autoViewport = true;
|
||||
_chart.getMeasureAxis().resetDomains();
|
||||
|
||||
_chart.draw(seriesList);
|
||||
|
||||
_chart.domainAxis.layout(domainAxisBounds, drawBounds);
|
||||
|
||||
_chart.getMeasureAxis().layout(measureAxisBounds, drawBounds);
|
||||
|
||||
_chart.lastListener.onAxisConfigured();
|
||||
}
|
||||
|
||||
setUpAll(() {
|
||||
drawBounds = new Rectangle<int>(0, 0, 100, 100);
|
||||
domainAxisBounds = new Rectangle<int>(0, 0, 100, 100);
|
||||
measureAxisBounds = new Rectangle<int>(0, 0, 100, 100);
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
_chart = _makeChart();
|
||||
|
||||
_series1 = new Series<MyRow, int>(
|
||||
id: 's1',
|
||||
data: [_s1D1, _s1D2, _s1D3],
|
||||
domainFn: (dynamic row, _) => row.campaign,
|
||||
measureFn: (dynamic row, _) => row.count,
|
||||
colorFn: (_, __) => MaterialPalette.blue.shadeDefault);
|
||||
|
||||
_series2 = new Series<MyRow, int>(
|
||||
id: 's2',
|
||||
data: [_s2D1, _s2D2, _s2D3],
|
||||
domainFn: (dynamic row, _) => row.campaign,
|
||||
measureFn: (dynamic row, _) => row.count,
|
||||
colorFn: (_, __) => MaterialPalette.red.shadeDefault);
|
||||
|
||||
_annotations1 = [
|
||||
new RangeAnnotationSegment(1, 2, RangeAnnotationAxisType.domain,
|
||||
startLabel: 'Ann 1'),
|
||||
new RangeAnnotationSegment(4, 5, RangeAnnotationAxisType.domain,
|
||||
color: MaterialPalette.gray.shade200, endLabel: 'Ann 2'),
|
||||
new RangeAnnotationSegment(5, 5.5, RangeAnnotationAxisType.measure,
|
||||
startLabel: 'Really long tick start label',
|
||||
endLabel: 'Really long tick end label'),
|
||||
new RangeAnnotationSegment(10, 15, RangeAnnotationAxisType.measure,
|
||||
startLabel: 'Ann 4 Start', endLabel: 'Ann 4 End'),
|
||||
new RangeAnnotationSegment(16, 22, RangeAnnotationAxisType.measure,
|
||||
startLabel: 'Ann 5 Start', endLabel: 'Ann 5 End'),
|
||||
];
|
||||
|
||||
_annotations2 = [
|
||||
new RangeAnnotationSegment(1, 2, RangeAnnotationAxisType.domain),
|
||||
new RangeAnnotationSegment(4, 5, RangeAnnotationAxisType.domain,
|
||||
color: MaterialPalette.gray.shade200),
|
||||
new RangeAnnotationSegment(8, 10, RangeAnnotationAxisType.domain,
|
||||
color: MaterialPalette.gray.shade300),
|
||||
];
|
||||
|
||||
_annotations3 = [
|
||||
new LineAnnotationSegment(1, RangeAnnotationAxisType.measure,
|
||||
startLabel: 'Ann 1 Start', endLabel: 'Ann 1 End'),
|
||||
new LineAnnotationSegment(4, RangeAnnotationAxisType.measure,
|
||||
startLabel: 'Ann 2 Start',
|
||||
endLabel: 'Ann 2 End',
|
||||
color: MaterialPalette.gray.shade200,
|
||||
dashPattern: _dashPattern),
|
||||
];
|
||||
});
|
||||
|
||||
group('RangeAnnotation', () {
|
||||
test('renders the annotations', () {
|
||||
// Setup
|
||||
final behavior = new RangeAnnotation<num>(_annotations1);
|
||||
final tester = new RangeAnnotationTester(behavior);
|
||||
behavior.attachTo(_chart);
|
||||
|
||||
final seriesList = [_series1, _series2];
|
||||
|
||||
// Act
|
||||
_drawSeriesList(_chart, seriesList);
|
||||
|
||||
// Verify
|
||||
expect(_chart.domainAxis.getLocation(2), equals(40.0));
|
||||
expect(
|
||||
tester.doesAnnotationExist(
|
||||
startPosition: 20.0,
|
||||
endPosition: 40.0,
|
||||
color: MaterialPalette.gray.shade100,
|
||||
startLabel: 'Ann 1',
|
||||
labelAnchor: AnnotationLabelAnchor.end,
|
||||
labelDirection: AnnotationLabelDirection.vertical,
|
||||
labelPosition: AnnotationLabelPosition.auto),
|
||||
equals(true));
|
||||
expect(
|
||||
tester.doesAnnotationExist(
|
||||
startPosition: 80.0,
|
||||
endPosition: 100.0,
|
||||
color: MaterialPalette.gray.shade200,
|
||||
endLabel: 'Ann 2',
|
||||
labelAnchor: AnnotationLabelAnchor.end,
|
||||
labelDirection: AnnotationLabelDirection.vertical,
|
||||
labelPosition: AnnotationLabelPosition.auto),
|
||||
equals(true));
|
||||
|
||||
// Verify measure annotations
|
||||
expect(_chart.getMeasureAxis().getLocation(11).round(), equals(33));
|
||||
expect(
|
||||
tester.doesAnnotationExist(
|
||||
startPosition: 0.0,
|
||||
endPosition: 2.78,
|
||||
color: MaterialPalette.gray.shade100,
|
||||
startLabel: 'Really long tick start label',
|
||||
endLabel: 'Really long tick end label',
|
||||
labelAnchor: AnnotationLabelAnchor.end,
|
||||
labelDirection: AnnotationLabelDirection.horizontal,
|
||||
labelPosition: AnnotationLabelPosition.auto),
|
||||
equals(true));
|
||||
expect(
|
||||
tester.doesAnnotationExist(
|
||||
startPosition: 27.78,
|
||||
endPosition: 55.56,
|
||||
color: MaterialPalette.gray.shade100,
|
||||
startLabel: 'Ann 4 Start',
|
||||
endLabel: 'Ann 4 End',
|
||||
labelAnchor: AnnotationLabelAnchor.end,
|
||||
labelDirection: AnnotationLabelDirection.horizontal,
|
||||
labelPosition: AnnotationLabelPosition.auto),
|
||||
equals(true));
|
||||
expect(
|
||||
tester.doesAnnotationExist(
|
||||
startPosition: 61.11,
|
||||
endPosition: 94.44,
|
||||
color: MaterialPalette.gray.shade100,
|
||||
startLabel: 'Ann 5 Start',
|
||||
endLabel: 'Ann 5 End',
|
||||
labelAnchor: AnnotationLabelAnchor.end,
|
||||
labelDirection: AnnotationLabelDirection.horizontal,
|
||||
labelPosition: AnnotationLabelPosition.auto),
|
||||
equals(true));
|
||||
});
|
||||
|
||||
test('extends the domain axis when annotations fall outside the range', () {
|
||||
// Setup
|
||||
final behavior = new RangeAnnotation<num>(_annotations2);
|
||||
final tester = new RangeAnnotationTester(behavior);
|
||||
behavior.attachTo(_chart);
|
||||
|
||||
final seriesList = [_series1, _series2];
|
||||
|
||||
// Act
|
||||
_drawSeriesList(_chart, seriesList);
|
||||
|
||||
// Verify
|
||||
expect(_chart.domainAxis.getLocation(2), equals(20.0));
|
||||
expect(
|
||||
tester.doesAnnotationExist(
|
||||
startPosition: 10.0,
|
||||
endPosition: 20.0,
|
||||
color: MaterialPalette.gray.shade100,
|
||||
labelAnchor: AnnotationLabelAnchor.end,
|
||||
labelDirection: AnnotationLabelDirection.vertical,
|
||||
labelPosition: AnnotationLabelPosition.auto),
|
||||
equals(true));
|
||||
expect(
|
||||
tester.doesAnnotationExist(
|
||||
startPosition: 40.0,
|
||||
endPosition: 50.0,
|
||||
color: MaterialPalette.gray.shade200,
|
||||
labelAnchor: AnnotationLabelAnchor.end,
|
||||
labelDirection: AnnotationLabelDirection.vertical,
|
||||
labelPosition: AnnotationLabelPosition.auto),
|
||||
equals(true));
|
||||
expect(
|
||||
tester.doesAnnotationExist(
|
||||
startPosition: 80.0,
|
||||
endPosition: 100.0,
|
||||
color: MaterialPalette.gray.shade300,
|
||||
labelAnchor: AnnotationLabelAnchor.end,
|
||||
labelDirection: AnnotationLabelDirection.vertical,
|
||||
labelPosition: AnnotationLabelPosition.auto),
|
||||
equals(true));
|
||||
});
|
||||
|
||||
test('test dash pattern equality', () {
|
||||
// Setup
|
||||
final behavior = new RangeAnnotation<num>(_annotations3);
|
||||
final tester = new RangeAnnotationTester(behavior);
|
||||
behavior.attachTo(_chart);
|
||||
|
||||
final seriesList = [_series1, _series2];
|
||||
|
||||
// Act
|
||||
_drawSeriesList(_chart, seriesList);
|
||||
|
||||
// Verify
|
||||
expect(_chart.domainAxis.getLocation(2), equals(40.0));
|
||||
expect(
|
||||
tester.doesAnnotationExist(
|
||||
startPosition: 0.0,
|
||||
endPosition: 0.0,
|
||||
color: MaterialPalette.gray.shade100,
|
||||
startLabel: 'Ann 1 Start',
|
||||
endLabel: 'Ann 1 End',
|
||||
labelAnchor: AnnotationLabelAnchor.end,
|
||||
labelDirection: AnnotationLabelDirection.horizontal,
|
||||
labelPosition: AnnotationLabelPosition.auto),
|
||||
equals(true));
|
||||
expect(
|
||||
tester.doesAnnotationExist(
|
||||
startPosition: 13.64,
|
||||
endPosition: 13.64,
|
||||
color: MaterialPalette.gray.shade200,
|
||||
dashPattern: _dashPattern,
|
||||
startLabel: 'Ann 2 Start',
|
||||
endLabel: 'Ann 2 End',
|
||||
labelAnchor: AnnotationLabelAnchor.end,
|
||||
labelDirection: AnnotationLabelDirection.horizontal,
|
||||
labelPosition: AnnotationLabelPosition.auto),
|
||||
equals(true));
|
||||
});
|
||||
|
||||
test('cleans up', () {
|
||||
// Setup
|
||||
final behavior = new RangeAnnotation<num>(_annotations2);
|
||||
behavior.attachTo(_chart);
|
||||
|
||||
// Act
|
||||
behavior.removeFrom(_chart);
|
||||
|
||||
// Verify
|
||||
expect(_chart.lastListener, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MyRow {
|
||||
final int campaign;
|
||||
final int count;
|
||||
MyRow(this.campaign, this.count);
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:charts_common/src/chart/common/base_chart.dart';
|
||||
import 'package:charts_common/src/chart/common/behavior/selection/lock_selection.dart';
|
||||
import 'package:charts_common/src/chart/common/selection_model/selection_model.dart';
|
||||
import 'package:charts_common/src/common/gesture_listener.dart';
|
||||
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockChart extends Mock implements BaseChart {
|
||||
GestureListener lastListener;
|
||||
|
||||
@override
|
||||
GestureListener addGestureListener(GestureListener listener) {
|
||||
lastListener = listener;
|
||||
return listener;
|
||||
}
|
||||
|
||||
@override
|
||||
void removeGestureListener(GestureListener listener) {
|
||||
expect(listener, equals(lastListener));
|
||||
lastListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
class MockSelectionModel extends Mock implements MutableSelectionModel {
|
||||
bool locked = false;
|
||||
}
|
||||
|
||||
void main() {
|
||||
MockChart _chart;
|
||||
MockSelectionModel _hoverSelectionModel;
|
||||
MockSelectionModel _clickSelectionModel;
|
||||
|
||||
LockSelection _makeLockSelectionBehavior(
|
||||
SelectionModelType selectionModelType) {
|
||||
LockSelection behavior =
|
||||
new LockSelection(selectionModelType: selectionModelType);
|
||||
|
||||
behavior.attachTo(_chart);
|
||||
|
||||
return behavior;
|
||||
}
|
||||
|
||||
_setupChart({Point<double> forPoint, bool isWithinRenderer}) {
|
||||
if (isWithinRenderer != null) {
|
||||
when(_chart.pointWithinRenderer(forPoint)).thenReturn(isWithinRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
_hoverSelectionModel = new MockSelectionModel();
|
||||
_clickSelectionModel = new MockSelectionModel();
|
||||
|
||||
_chart = new MockChart();
|
||||
when(_chart.getSelectionModel(SelectionModelType.info))
|
||||
.thenReturn(_hoverSelectionModel);
|
||||
when(_chart.getSelectionModel(SelectionModelType.action))
|
||||
.thenReturn(_clickSelectionModel);
|
||||
});
|
||||
|
||||
group('LockSelection trigger handling', () {
|
||||
test('can lock model with a selection', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeLockSelectionBehavior(SelectionModelType.info);
|
||||
Point<double> point = new Point(100.0, 100.0);
|
||||
_setupChart(forPoint: point, isWithinRenderer: true);
|
||||
|
||||
when(_hoverSelectionModel.hasAnySelection).thenReturn(true);
|
||||
|
||||
// Act
|
||||
_chart.lastListener.onTapTest(point);
|
||||
_chart.lastListener.onTap(point);
|
||||
|
||||
// Validate
|
||||
verify(_hoverSelectionModel.hasAnySelection);
|
||||
expect(_hoverSelectionModel.locked, equals(true));
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
});
|
||||
|
||||
test('can lock and unlock model', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeLockSelectionBehavior(SelectionModelType.info);
|
||||
Point<double> point = new Point(100.0, 100.0);
|
||||
_setupChart(forPoint: point, isWithinRenderer: true);
|
||||
|
||||
when(_hoverSelectionModel.hasAnySelection).thenReturn(true);
|
||||
|
||||
// Act
|
||||
_chart.lastListener.onTapTest(point);
|
||||
_chart.lastListener.onTap(point);
|
||||
|
||||
// Validate
|
||||
verify(_hoverSelectionModel.hasAnySelection);
|
||||
expect(_hoverSelectionModel.locked, equals(true));
|
||||
|
||||
// Act
|
||||
_chart.lastListener.onTapTest(point);
|
||||
_chart.lastListener.onTap(point);
|
||||
|
||||
// Validate
|
||||
verify(_hoverSelectionModel.clearSelection());
|
||||
expect(_hoverSelectionModel.locked, equals(false));
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
});
|
||||
|
||||
test('does not lock model with empty selection', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeLockSelectionBehavior(SelectionModelType.info);
|
||||
Point<double> point = new Point(100.0, 100.0);
|
||||
_setupChart(forPoint: point, isWithinRenderer: true);
|
||||
|
||||
when(_hoverSelectionModel.hasAnySelection).thenReturn(false);
|
||||
|
||||
// Act
|
||||
_chart.lastListener.onTapTest(point);
|
||||
_chart.lastListener.onTap(point);
|
||||
|
||||
// Validate
|
||||
verify(_hoverSelectionModel.hasAnySelection);
|
||||
expect(_hoverSelectionModel.locked, equals(false));
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
});
|
||||
});
|
||||
|
||||
group('Cleanup', () {
|
||||
test('detach removes listener', () {
|
||||
// Setup
|
||||
final behavior = _makeLockSelectionBehavior(SelectionModelType.info);
|
||||
Point<double> point = new Point(100.0, 100.0);
|
||||
_setupChart(forPoint: point, isWithinRenderer: true);
|
||||
expect(_chart.lastListener, isNotNull);
|
||||
|
||||
// Act
|
||||
behavior.removeFrom(_chart);
|
||||
|
||||
// Validate
|
||||
expect(_chart.lastListener, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,491 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:charts_common/src/chart/common/base_chart.dart';
|
||||
import 'package:charts_common/src/chart/common/behavior/selection/select_nearest.dart';
|
||||
import 'package:charts_common/src/chart/common/behavior/selection/selection_trigger.dart';
|
||||
import 'package:charts_common/src/chart/common/datum_details.dart';
|
||||
import 'package:charts_common/src/chart/common/processed_series.dart';
|
||||
import 'package:charts_common/src/chart/common/series_datum.dart';
|
||||
import 'package:charts_common/src/chart/common/selection_model/selection_model.dart';
|
||||
import 'package:charts_common/src/common/gesture_listener.dart';
|
||||
import 'package:charts_common/src/data/series.dart';
|
||||
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockChart extends Mock implements BaseChart<String> {
|
||||
GestureListener lastListener;
|
||||
|
||||
@override
|
||||
GestureListener addGestureListener(GestureListener listener) {
|
||||
lastListener = listener;
|
||||
return listener;
|
||||
}
|
||||
|
||||
@override
|
||||
void removeGestureListener(GestureListener listener) {
|
||||
expect(listener, equals(lastListener));
|
||||
lastListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
class MockSelectionModel extends Mock implements MutableSelectionModel<String> {
|
||||
}
|
||||
|
||||
void main() {
|
||||
MockChart _chart;
|
||||
MockSelectionModel _hoverSelectionModel;
|
||||
MockSelectionModel _clickSelectionModel;
|
||||
List<String> _series1Data;
|
||||
List<String> _series2Data;
|
||||
MutableSeries<String> _series1;
|
||||
MutableSeries<String> _series2;
|
||||
DatumDetails<String> _details1;
|
||||
DatumDetails<String> _details1Series2;
|
||||
DatumDetails<String> _details2;
|
||||
DatumDetails<String> _details3;
|
||||
|
||||
SelectNearest<String> _makeBehavior(
|
||||
SelectionModelType selectionModelType, SelectionTrigger eventTrigger,
|
||||
{bool expandToDomain,
|
||||
bool selectClosestSeries,
|
||||
int maximumDomainDistancePx}) {
|
||||
SelectNearest<String> behavior = new SelectNearest<String>(
|
||||
selectionModelType: selectionModelType,
|
||||
expandToDomain: expandToDomain,
|
||||
selectClosestSeries: selectClosestSeries,
|
||||
eventTrigger: eventTrigger,
|
||||
maximumDomainDistancePx: maximumDomainDistancePx);
|
||||
|
||||
behavior.attachTo(_chart);
|
||||
|
||||
return behavior;
|
||||
}
|
||||
|
||||
_setupChart(
|
||||
{Point<double> forPoint,
|
||||
bool isWithinRenderer,
|
||||
List<DatumDetails<String>> respondWithDetails,
|
||||
List<MutableSeries<String>> seriesList}) {
|
||||
if (isWithinRenderer != null) {
|
||||
when(_chart.pointWithinRenderer(forPoint)).thenReturn(isWithinRenderer);
|
||||
}
|
||||
if (respondWithDetails != null) {
|
||||
when(_chart.getNearestDatumDetailPerSeries(forPoint, true))
|
||||
.thenReturn(respondWithDetails);
|
||||
}
|
||||
if (seriesList != null) {
|
||||
when(_chart.currentSeriesList).thenReturn(seriesList);
|
||||
}
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
_hoverSelectionModel = new MockSelectionModel();
|
||||
_clickSelectionModel = new MockSelectionModel();
|
||||
|
||||
_chart = new MockChart();
|
||||
when(_chart.getSelectionModel(SelectionModelType.info))
|
||||
.thenReturn(_hoverSelectionModel);
|
||||
when(_chart.getSelectionModel(SelectionModelType.action))
|
||||
.thenReturn(_clickSelectionModel);
|
||||
|
||||
_series1Data = ['myDomain1', 'myDomain2', 'myDomain3'];
|
||||
|
||||
_series1 = new MutableSeries<String>(new Series(
|
||||
id: 'mySeries1',
|
||||
data: ['myDatum1', 'myDatum2', 'myDatum3'],
|
||||
domainFn: (_, int i) => _series1Data[i],
|
||||
measureFn: (_, __) {}));
|
||||
|
||||
_details1 = new DatumDetails(
|
||||
datum: 'myDatum1',
|
||||
domain: 'myDomain1',
|
||||
series: _series1,
|
||||
domainDistance: 10.0,
|
||||
measureDistance: 20.0);
|
||||
_details2 = new DatumDetails(
|
||||
datum: 'myDatum2',
|
||||
domain: 'myDomain2',
|
||||
series: _series1,
|
||||
domainDistance: 10.0,
|
||||
measureDistance: 20.0);
|
||||
_details3 = new DatumDetails(
|
||||
datum: 'myDatum3',
|
||||
domain: 'myDomain3',
|
||||
series: _series1,
|
||||
domainDistance: 10.0,
|
||||
measureDistance: 20.0);
|
||||
|
||||
_series2Data = ['myDomain1'];
|
||||
|
||||
_series2 = new MutableSeries<String>(new Series(
|
||||
id: 'mySeries2',
|
||||
data: ['myDatum1s2'],
|
||||
domainFn: (_, int i) => _series2Data[i],
|
||||
measureFn: (_, __) {}));
|
||||
|
||||
_details1Series2 = new DatumDetails(
|
||||
datum: 'myDatum1s2',
|
||||
domain: 'myDomain1',
|
||||
series: _series2,
|
||||
domainDistance: 10.0,
|
||||
measureDistance: 20.0);
|
||||
});
|
||||
|
||||
tearDown(resetMockitoState);
|
||||
|
||||
group('SelectNearest trigger handling', () {
|
||||
test('single series selects detail', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionModelType.info, SelectionTrigger.hover,
|
||||
expandToDomain: true, selectClosestSeries: true);
|
||||
Point<double> point = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: point,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1],
|
||||
seriesList: [_series1]);
|
||||
|
||||
// Act
|
||||
_chart.lastListener.onHover(point);
|
||||
|
||||
// Validate
|
||||
verify(_hoverSelectionModel.updateSelection(
|
||||
[new SeriesDatum(_series1, _details1.datum)], [_series1]));
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
// Shouldn't be listening to anything else.
|
||||
expect(_chart.lastListener.onTap, isNull);
|
||||
expect(_chart.lastListener.onDragStart, isNull);
|
||||
});
|
||||
|
||||
test('can listen to tap', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionModelType.action, SelectionTrigger.tap,
|
||||
expandToDomain: true, selectClosestSeries: true);
|
||||
Point<double> point = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: point,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1],
|
||||
seriesList: [_series1]);
|
||||
|
||||
// Act
|
||||
_chart.lastListener.onTapTest(point);
|
||||
_chart.lastListener.onTap(point);
|
||||
|
||||
// Validate
|
||||
verify(_clickSelectionModel.updateSelection(
|
||||
[new SeriesDatum(_series1, _details1.datum)], [_series1]));
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
});
|
||||
|
||||
test('can listen to drag', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionModelType.info, SelectionTrigger.pressHold,
|
||||
expandToDomain: true, selectClosestSeries: true);
|
||||
|
||||
Point<double> startPoint = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: startPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1],
|
||||
seriesList: [_series1]);
|
||||
|
||||
Point<double> updatePoint1 = new Point(200.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint1,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1],
|
||||
seriesList: [_series1]);
|
||||
|
||||
Point<double> updatePoint2 = new Point(300.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint2,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details2],
|
||||
seriesList: [_series1]);
|
||||
|
||||
Point<double> endPoint = new Point(400.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: endPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3],
|
||||
seriesList: [_series1]);
|
||||
|
||||
// Act
|
||||
_chart.lastListener.onTapTest(startPoint);
|
||||
_chart.lastListener.onDragStart(startPoint);
|
||||
_chart.lastListener.onDragUpdate(updatePoint1, 1.0);
|
||||
_chart.lastListener.onDragUpdate(updatePoint2, 1.0);
|
||||
_chart.lastListener.onDragEnd(endPoint, 1.0, 0.0);
|
||||
|
||||
// Validate
|
||||
// details1 was tripped 2 times (startPoint & updatePoint1)
|
||||
verify(_hoverSelectionModel.updateSelection(
|
||||
[new SeriesDatum(_series1, _details1.datum)], [_series1])).called(2);
|
||||
// details2 was tripped for updatePoint2
|
||||
verify(_hoverSelectionModel.updateSelection(
|
||||
[new SeriesDatum(_series1, _details2.datum)], [_series1]));
|
||||
// dragEnd deselects even though we are over details3.
|
||||
verify(_hoverSelectionModel.updateSelection([], []));
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
});
|
||||
|
||||
test('can listen to drag after long press', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionModelType.info, SelectionTrigger.longPressHold,
|
||||
expandToDomain: true, selectClosestSeries: true);
|
||||
|
||||
Point<double> startPoint = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: startPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1],
|
||||
seriesList: [_series1]);
|
||||
|
||||
Point<double> updatePoint1 = new Point(200.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint1,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details2],
|
||||
seriesList: [_series1]);
|
||||
|
||||
Point<double> endPoint = new Point(400.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: endPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3],
|
||||
seriesList: [_series1]);
|
||||
|
||||
// Act 1
|
||||
_chart.lastListener.onTapTest(startPoint);
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
|
||||
// Act 2
|
||||
// verify no interaction yet.
|
||||
_chart.lastListener.onLongPress(startPoint);
|
||||
_chart.lastListener.onDragStart(startPoint);
|
||||
_chart.lastListener.onDragUpdate(updatePoint1, 1.0);
|
||||
_chart.lastListener.onDragEnd(endPoint, 1.0, 0.0);
|
||||
|
||||
// Validate
|
||||
// details1 was tripped 2 times (longPress & dragStart)
|
||||
verify(_hoverSelectionModel.updateSelection(
|
||||
[new SeriesDatum(_series1, _details1.datum)], [_series1])).called(2);
|
||||
verify(_hoverSelectionModel.updateSelection(
|
||||
[new SeriesDatum(_series1, _details2.datum)], [_series1]));
|
||||
// dragEnd deselects even though we are over details3.
|
||||
verify(_hoverSelectionModel.updateSelection([], []));
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
});
|
||||
|
||||
test('no trigger before long press', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionModelType.info, SelectionTrigger.longPressHold,
|
||||
expandToDomain: true, selectClosestSeries: true);
|
||||
|
||||
Point<double> startPoint = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: startPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1],
|
||||
seriesList: [_series1]);
|
||||
|
||||
Point<double> updatePoint1 = new Point(200.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint1,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details2],
|
||||
seriesList: [_series1]);
|
||||
|
||||
Point<double> endPoint = new Point(400.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: endPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3],
|
||||
seriesList: [_series1]);
|
||||
|
||||
// Act
|
||||
_chart.lastListener.onTapTest(startPoint);
|
||||
_chart.lastListener.onDragStart(startPoint);
|
||||
_chart.lastListener.onDragUpdate(updatePoint1, 1.0);
|
||||
_chart.lastListener.onDragEnd(endPoint, 1.0, 0.0);
|
||||
|
||||
// Validate
|
||||
// No interaction, didn't long press first.
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
});
|
||||
});
|
||||
|
||||
group('Details', () {
|
||||
test('expands to domain and includes closest series', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionModelType.info, SelectionTrigger.hover,
|
||||
expandToDomain: true, selectClosestSeries: true);
|
||||
Point<double> point = new Point(100.0, 100.0);
|
||||
_setupChart(forPoint: point, isWithinRenderer: true, respondWithDetails: [
|
||||
_details1,
|
||||
_details1Series2,
|
||||
], seriesList: [
|
||||
_series1,
|
||||
_series2
|
||||
]);
|
||||
|
||||
// Act
|
||||
_chart.lastListener.onHover(point);
|
||||
|
||||
// Validate
|
||||
verify(_hoverSelectionModel.updateSelection([
|
||||
new SeriesDatum(_series1, _details1.datum),
|
||||
new SeriesDatum(_series2, _details1Series2.datum)
|
||||
], [
|
||||
_series1
|
||||
]));
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
});
|
||||
|
||||
test('does not expand to domain', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionModelType.info, SelectionTrigger.hover,
|
||||
expandToDomain: false, selectClosestSeries: true);
|
||||
Point<double> point = new Point(100.0, 100.0);
|
||||
_setupChart(forPoint: point, isWithinRenderer: true, respondWithDetails: [
|
||||
_details1,
|
||||
_details1Series2,
|
||||
], seriesList: [
|
||||
_series1,
|
||||
_series2
|
||||
]);
|
||||
|
||||
// Act
|
||||
_chart.lastListener.onHover(point);
|
||||
|
||||
// Validate
|
||||
verify(_hoverSelectionModel.updateSelection(
|
||||
[new SeriesDatum(_series1, _details1.datum)], [_series1]));
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
});
|
||||
|
||||
test('does not include closest series', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionModelType.info, SelectionTrigger.hover,
|
||||
expandToDomain: true, selectClosestSeries: false);
|
||||
Point<double> point = new Point(100.0, 100.0);
|
||||
_setupChart(forPoint: point, isWithinRenderer: true, respondWithDetails: [
|
||||
_details1,
|
||||
_details1Series2,
|
||||
], seriesList: [
|
||||
_series1,
|
||||
_series2
|
||||
]);
|
||||
|
||||
// Act
|
||||
_chart.lastListener.onHover(point);
|
||||
|
||||
// Validate
|
||||
verify(_hoverSelectionModel.updateSelection([
|
||||
new SeriesDatum(_series1, _details1.datum),
|
||||
new SeriesDatum(_series2, _details1Series2.datum)
|
||||
], []));
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
});
|
||||
|
||||
test('does not include overlay series', () {
|
||||
// Setup chart with an overlay series.
|
||||
_series2.overlaySeries = true;
|
||||
|
||||
_makeBehavior(SelectionModelType.info, SelectionTrigger.hover,
|
||||
expandToDomain: true, selectClosestSeries: true);
|
||||
Point<double> point = new Point(100.0, 100.0);
|
||||
_setupChart(forPoint: point, isWithinRenderer: true, respondWithDetails: [
|
||||
_details1,
|
||||
_details1Series2,
|
||||
], seriesList: [
|
||||
_series1,
|
||||
_series2
|
||||
]);
|
||||
|
||||
// Act
|
||||
_chart.lastListener.onHover(point);
|
||||
|
||||
// Validate
|
||||
verify(_hoverSelectionModel.updateSelection([
|
||||
new SeriesDatum(_series1, _details1.datum),
|
||||
], [
|
||||
_series1
|
||||
]));
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
});
|
||||
|
||||
test('selection does not exceed maximumDomainDistancePx', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionModelType.info, SelectionTrigger.hover,
|
||||
expandToDomain: true,
|
||||
selectClosestSeries: true,
|
||||
maximumDomainDistancePx: 1);
|
||||
Point<double> point = new Point(100.0, 100.0);
|
||||
_setupChart(forPoint: point, isWithinRenderer: true, respondWithDetails: [
|
||||
_details1,
|
||||
_details1Series2,
|
||||
], seriesList: [
|
||||
_series1,
|
||||
_series2
|
||||
]);
|
||||
|
||||
// Act
|
||||
_chart.lastListener.onHover(point);
|
||||
|
||||
// Validate
|
||||
verify(_hoverSelectionModel.updateSelection([], []));
|
||||
verifyNoMoreInteractions(_hoverSelectionModel);
|
||||
verifyNoMoreInteractions(_clickSelectionModel);
|
||||
});
|
||||
});
|
||||
|
||||
group('Cleanup', () {
|
||||
test('detach removes listener', () {
|
||||
// Setup
|
||||
SelectNearest behavior = _makeBehavior(
|
||||
SelectionModelType.info, SelectionTrigger.hover,
|
||||
expandToDomain: true, selectClosestSeries: true);
|
||||
Point<double> point = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: point,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1],
|
||||
seriesList: [_series1]);
|
||||
expect(_chart.lastListener, isNotNull);
|
||||
|
||||
// Act
|
||||
behavior.removeFrom(_chart);
|
||||
|
||||
// Validate
|
||||
expect(_chart.lastListener, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'package:charts_common/src/chart/cartesian/axis/axis.dart';
|
||||
import 'package:charts_common/src/chart/common/base_chart.dart';
|
||||
import 'package:charts_common/src/chart/common/processed_series.dart';
|
||||
import 'package:charts_common/src/chart/common/series_datum.dart';
|
||||
import 'package:charts_common/src/chart/common/series_renderer.dart';
|
||||
import 'package:charts_common/src/chart/common/behavior/legend/legend_entry_generator.dart';
|
||||
import 'package:charts_common/src/chart/common/behavior/legend/series_legend.dart';
|
||||
import 'package:charts_common/src/chart/common/datum_details.dart';
|
||||
import 'package:charts_common/src/chart/common/selection_model/selection_model.dart';
|
||||
import 'package:charts_common/src/common/color.dart';
|
||||
import 'package:charts_common/src/data/series.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class ConcreteChart extends BaseChart<String> {
|
||||
List<MutableSeries<String>> _seriesList;
|
||||
|
||||
ConcreteChart(this._seriesList);
|
||||
|
||||
@override
|
||||
SeriesRenderer<String> makeDefaultRenderer() => null;
|
||||
|
||||
@override
|
||||
List<MutableSeries<String>> get currentSeriesList => _seriesList;
|
||||
|
||||
@override
|
||||
List<DatumDetails<String>> getDatumDetails(SelectionModelType _) => null;
|
||||
|
||||
set seriesList(List<MutableSeries<String>> seriesList) {
|
||||
_seriesList = seriesList;
|
||||
}
|
||||
|
||||
void callOnDraw() {
|
||||
fireOnDraw(_seriesList);
|
||||
}
|
||||
|
||||
void callOnPreProcess() {
|
||||
fireOnPreprocess(_seriesList);
|
||||
}
|
||||
|
||||
void callOnPostProcess() {
|
||||
fireOnPostprocess(_seriesList);
|
||||
}
|
||||
}
|
||||
|
||||
class ConcreteSeriesLegend<D> extends SeriesLegend<D> {
|
||||
ConcreteSeriesLegend(
|
||||
{SelectionModelType selectionModelType,
|
||||
LegendEntryGenerator<D> legendEntryGenerator})
|
||||
: super(
|
||||
selectionModelType: selectionModelType,
|
||||
legendEntryGenerator: legendEntryGenerator);
|
||||
|
||||
@override
|
||||
bool isSeriesRenderer = false;
|
||||
|
||||
@override
|
||||
void hideSeries(String seriesId) {
|
||||
super.hideSeries(seriesId);
|
||||
}
|
||||
|
||||
@override
|
||||
void showSeries(String seriesId) {
|
||||
super.showSeries(seriesId);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSeriesHidden(String seriesId) {
|
||||
return super.isSeriesHidden(seriesId);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
MutableSeries<String> series1;
|
||||
final s1D1 = new MyRow('s1d1', 11);
|
||||
final s1D2 = new MyRow('s1d2', 12);
|
||||
final s1D3 = new MyRow('s1d3', 13);
|
||||
|
||||
MutableSeries<String> series2;
|
||||
final s2D1 = new MyRow('s2d1', 21);
|
||||
final s2D2 = new MyRow('s2d2', 22);
|
||||
final s2D3 = new MyRow('s2d3', 23);
|
||||
|
||||
final blue = new Color(r: 0x21, g: 0x96, b: 0xF3);
|
||||
final red = new Color(r: 0xF4, g: 0x43, b: 0x36);
|
||||
|
||||
ConcreteChart chart;
|
||||
|
||||
setUp(() {
|
||||
series1 = new MutableSeries(new Series<MyRow, String>(
|
||||
id: 's1',
|
||||
data: [s1D1, s1D2, s1D3],
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.count,
|
||||
colorFn: (_, __) => blue));
|
||||
|
||||
series2 = new MutableSeries(new Series<MyRow, String>(
|
||||
id: 's2',
|
||||
data: [s2D1, s2D2, s2D3],
|
||||
domainFn: (MyRow row, _) => row.campaign,
|
||||
measureFn: (MyRow row, _) => row.count,
|
||||
colorFn: (_, __) => red));
|
||||
});
|
||||
|
||||
test('Legend entries created on chart post process', () {
|
||||
final seriesList = [series1, series2];
|
||||
final selectionType = SelectionModelType.info;
|
||||
final legend = new SeriesLegend<String>(selectionModelType: selectionType);
|
||||
|
||||
chart = new ConcreteChart(seriesList);
|
||||
legend.attachTo(chart);
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
chart.callOnPostProcess();
|
||||
|
||||
final legendEntries = legend.legendState.legendEntries;
|
||||
expect(legendEntries, hasLength(2));
|
||||
expect(legendEntries[0].series, equals(series1));
|
||||
expect(legendEntries[0].label, equals('s1'));
|
||||
expect(legendEntries[0].color, equals(blue));
|
||||
expect(legendEntries[0].isSelected, isFalse);
|
||||
|
||||
expect(legendEntries[1].series, equals(series2));
|
||||
expect(legendEntries[1].label, equals('s2'));
|
||||
expect(legendEntries[1].color, equals(red));
|
||||
expect(legendEntries[1].isSelected, isFalse);
|
||||
});
|
||||
|
||||
test('default hidden series are removed from list during pre process', () {
|
||||
final seriesList = [series1, series2];
|
||||
final selectionType = SelectionModelType.info;
|
||||
final legend =
|
||||
new ConcreteSeriesLegend<String>(selectionModelType: selectionType);
|
||||
|
||||
legend.defaultHiddenSeries = ['s2'];
|
||||
|
||||
chart = new ConcreteChart(seriesList);
|
||||
legend.attachTo(chart);
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
|
||||
expect(legend.isSeriesHidden('s1'), isFalse);
|
||||
expect(legend.isSeriesHidden('s2'), isTrue);
|
||||
|
||||
expect(seriesList, hasLength(1));
|
||||
expect(seriesList[0].id, equals('s1'));
|
||||
});
|
||||
|
||||
test('hidden series are removed from list after chart pre process', () {
|
||||
final seriesList = [series1, series2];
|
||||
final selectionType = SelectionModelType.info;
|
||||
final legend =
|
||||
new ConcreteSeriesLegend<String>(selectionModelType: selectionType);
|
||||
|
||||
chart = new ConcreteChart(seriesList);
|
||||
legend.attachTo(chart);
|
||||
legend.hideSeries('s1');
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
|
||||
expect(legend.isSeriesHidden('s1'), isTrue);
|
||||
expect(legend.isSeriesHidden('s2'), isFalse);
|
||||
|
||||
expect(seriesList, hasLength(1));
|
||||
expect(seriesList[0].id, equals('s2'));
|
||||
});
|
||||
|
||||
test('hidden and re-shown series is in the list after chart pre process', () {
|
||||
final seriesList = [series1, series2];
|
||||
final seriesList2 = [series1, series2];
|
||||
final selectionType = SelectionModelType.info;
|
||||
final legend =
|
||||
new ConcreteSeriesLegend<String>(selectionModelType: selectionType);
|
||||
|
||||
chart = new ConcreteChart(seriesList);
|
||||
legend.attachTo(chart);
|
||||
|
||||
// First hide the series.
|
||||
legend.hideSeries('s1');
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
|
||||
expect(legend.isSeriesHidden('s1'), isTrue);
|
||||
expect(legend.isSeriesHidden('s2'), isFalse);
|
||||
|
||||
expect(seriesList, hasLength(1));
|
||||
expect(seriesList[0].id, equals('s2'));
|
||||
|
||||
// Then un-hide the series. This second list imitates the behavior of the
|
||||
// chart, which creates a fresh copy of the original data from the user
|
||||
// during each draw cycle.
|
||||
legend.showSeries('s1');
|
||||
chart.seriesList = seriesList2;
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
|
||||
expect(legend.isSeriesHidden('s1'), isFalse);
|
||||
expect(legend.isSeriesHidden('s2'), isFalse);
|
||||
|
||||
expect(seriesList2, hasLength(2));
|
||||
expect(seriesList2[0].id, equals('s1'));
|
||||
expect(seriesList2[1].id, equals('s2'));
|
||||
});
|
||||
|
||||
test('selected series legend entry is updated', () {
|
||||
final seriesList = [series1, series2];
|
||||
final selectionType = SelectionModelType.info;
|
||||
final legend = new SeriesLegend<String>(selectionModelType: selectionType);
|
||||
|
||||
chart = new ConcreteChart(seriesList);
|
||||
legend.attachTo(chart);
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
chart.callOnPostProcess();
|
||||
chart.getSelectionModel(selectionType).updateSelection([], [series1]);
|
||||
|
||||
final legendEntries = legend.legendState.legendEntries;
|
||||
expect(legendEntries, hasLength(2));
|
||||
expect(legendEntries[0].series, equals(series1));
|
||||
expect(legendEntries[0].label, equals('s1'));
|
||||
expect(legendEntries[0].color, equals(blue));
|
||||
expect(legendEntries[0].isSelected, isTrue);
|
||||
|
||||
expect(legendEntries[1].series, equals(series2));
|
||||
expect(legendEntries[1].label, equals('s2'));
|
||||
expect(legendEntries[1].color, equals(red));
|
||||
expect(legendEntries[1].isSelected, isFalse);
|
||||
});
|
||||
|
||||
test('hidden series removed from chart and later readded is visible', () {
|
||||
final seriesList = [series1, series2];
|
||||
final selectionType = SelectionModelType.info;
|
||||
final legend =
|
||||
new ConcreteSeriesLegend<String>(selectionModelType: selectionType);
|
||||
|
||||
chart = new ConcreteChart(seriesList);
|
||||
legend.attachTo(chart);
|
||||
|
||||
// First hide the series.
|
||||
legend.hideSeries('s1');
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
|
||||
expect(legend.isSeriesHidden('s1'), isTrue);
|
||||
expect(legend.isSeriesHidden('s2'), isFalse);
|
||||
|
||||
expect(seriesList, hasLength(1));
|
||||
expect(seriesList[0].id, equals('s2'));
|
||||
|
||||
// Validate that drawing the same set of series again maintains the hidden
|
||||
// states.
|
||||
final seriesList2 = [series1, series2];
|
||||
chart.seriesList = seriesList2;
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
|
||||
expect(legend.isSeriesHidden('s1'), isTrue);
|
||||
expect(legend.isSeriesHidden('s2'), isFalse);
|
||||
|
||||
expect(seriesList2, hasLength(1));
|
||||
expect(seriesList2[0].id, equals('s2'));
|
||||
|
||||
// Next, redraw the chart with only the visible series2.
|
||||
final seriesList3 = [series2];
|
||||
|
||||
chart.seriesList = seriesList3;
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
|
||||
expect(legend.isSeriesHidden('s2'), isFalse);
|
||||
|
||||
expect(seriesList3, hasLength(1));
|
||||
expect(seriesList3[0].id, equals('s2'));
|
||||
|
||||
// Finally, add series1 back to the chart, and validate that it is not
|
||||
// hidden.
|
||||
final seriesList4 = [series1, series2];
|
||||
chart.seriesList = seriesList4;
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
|
||||
expect(legend.isSeriesHidden('s1'), isFalse);
|
||||
expect(legend.isSeriesHidden('s2'), isFalse);
|
||||
|
||||
expect(seriesList4, hasLength(2));
|
||||
expect(seriesList4[0].id, equals('s1'));
|
||||
expect(seriesList4[1].id, equals('s2'));
|
||||
});
|
||||
|
||||
test('generated legend entries use provided formatters', () {
|
||||
final seriesList = [series1, series2];
|
||||
final selectionType = SelectionModelType.info;
|
||||
final measureFormatter =
|
||||
(num value) => 'measure ${value?.toStringAsFixed(0)}';
|
||||
final secondaryMeasureFormatter =
|
||||
(num value) => 'secondary ${value?.toStringAsFixed(0)}';
|
||||
final legend = new SeriesLegend<String>(
|
||||
selectionModelType: selectionType,
|
||||
measureFormatter: measureFormatter,
|
||||
secondaryMeasureFormatter: secondaryMeasureFormatter);
|
||||
|
||||
series2.setAttr(measureAxisIdKey, 'secondaryMeasureAxisId');
|
||||
chart = new ConcreteChart(seriesList);
|
||||
legend.attachTo(chart);
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
chart.callOnPostProcess();
|
||||
chart.getSelectionModel(selectionType).updateSelection(
|
||||
[new SeriesDatum(series1, s1D1), new SeriesDatum(series2, s2D1)],
|
||||
[series1, series2]);
|
||||
|
||||
final legendEntries = legend.legendState.legendEntries;
|
||||
expect(legendEntries, hasLength(2));
|
||||
expect(legendEntries[0].series, equals(series1));
|
||||
expect(legendEntries[0].label, equals('s1'));
|
||||
expect(legendEntries[0].isSelected, isTrue);
|
||||
expect(legendEntries[0].value, equals(11.0));
|
||||
expect(legendEntries[0].formattedValue, equals('measure 11'));
|
||||
|
||||
expect(legendEntries[1].series, equals(series2));
|
||||
expect(legendEntries[1].label, equals('s2'));
|
||||
expect(legendEntries[1].isSelected, isTrue);
|
||||
expect(legendEntries[1].value, equals(21.0));
|
||||
expect(legendEntries[1].formattedValue, equals('secondary 21'));
|
||||
});
|
||||
|
||||
test('series legend - show measure sum when there is no selection', () {
|
||||
final seriesList = [series1, series2];
|
||||
final selectionType = SelectionModelType.info;
|
||||
final measureFormatter = (num value) => '${value?.toStringAsFixed(0)}';
|
||||
final legend = new SeriesLegend<String>(
|
||||
selectionModelType: selectionType,
|
||||
legendDefaultMeasure: LegendDefaultMeasure.sum,
|
||||
measureFormatter: measureFormatter);
|
||||
|
||||
chart = new ConcreteChart(seriesList);
|
||||
legend.attachTo(chart);
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
chart.callOnPostProcess();
|
||||
|
||||
final legendEntries = legend.legendState.legendEntries;
|
||||
expect(legendEntries, hasLength(2));
|
||||
expect(legendEntries[0].series, equals(series1));
|
||||
expect(legendEntries[0].label, equals('s1'));
|
||||
expect(legendEntries[0].color, equals(blue));
|
||||
expect(legendEntries[0].isSelected, isFalse);
|
||||
expect(legendEntries[0].value, equals(36.0));
|
||||
expect(legendEntries[0].formattedValue, equals('36'));
|
||||
|
||||
expect(legendEntries[1].series, equals(series2));
|
||||
expect(legendEntries[1].label, equals('s2'));
|
||||
expect(legendEntries[1].color, equals(red));
|
||||
expect(legendEntries[1].isSelected, isFalse);
|
||||
expect(legendEntries[1].value, equals(66.0));
|
||||
expect(legendEntries[1].formattedValue, equals('66'));
|
||||
});
|
||||
|
||||
test('series legend - show measure average when there is no selection', () {
|
||||
final seriesList = [series1, series2];
|
||||
final selectionType = SelectionModelType.info;
|
||||
final measureFormatter = (num value) => '${value?.toStringAsFixed(0)}';
|
||||
final legend = new SeriesLegend<String>(
|
||||
selectionModelType: selectionType,
|
||||
legendDefaultMeasure: LegendDefaultMeasure.average,
|
||||
measureFormatter: measureFormatter);
|
||||
|
||||
chart = new ConcreteChart(seriesList);
|
||||
legend.attachTo(chart);
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
chart.callOnPostProcess();
|
||||
|
||||
final legendEntries = legend.legendState.legendEntries;
|
||||
expect(legendEntries, hasLength(2));
|
||||
expect(legendEntries[0].series, equals(series1));
|
||||
expect(legendEntries[0].label, equals('s1'));
|
||||
expect(legendEntries[0].color, equals(blue));
|
||||
expect(legendEntries[0].isSelected, isFalse);
|
||||
expect(legendEntries[0].value, equals(12.0));
|
||||
expect(legendEntries[0].formattedValue, equals('12'));
|
||||
|
||||
expect(legendEntries[1].series, equals(series2));
|
||||
expect(legendEntries[1].label, equals('s2'));
|
||||
expect(legendEntries[1].color, equals(red));
|
||||
expect(legendEntries[1].isSelected, isFalse);
|
||||
expect(legendEntries[1].value, equals(22.0));
|
||||
expect(legendEntries[1].formattedValue, equals('22'));
|
||||
});
|
||||
|
||||
test('series legend - show first measure when there is no selection', () {
|
||||
final seriesList = [series1, series2];
|
||||
final selectionType = SelectionModelType.info;
|
||||
final measureFormatter = (num value) => '${value?.toStringAsFixed(0)}';
|
||||
final legend = new SeriesLegend<String>(
|
||||
selectionModelType: selectionType,
|
||||
legendDefaultMeasure: LegendDefaultMeasure.firstValue,
|
||||
measureFormatter: measureFormatter);
|
||||
|
||||
chart = new ConcreteChart(seriesList);
|
||||
legend.attachTo(chart);
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
chart.callOnPostProcess();
|
||||
|
||||
final legendEntries = legend.legendState.legendEntries;
|
||||
expect(legendEntries, hasLength(2));
|
||||
expect(legendEntries[0].series, equals(series1));
|
||||
expect(legendEntries[0].label, equals('s1'));
|
||||
expect(legendEntries[0].color, equals(blue));
|
||||
expect(legendEntries[0].isSelected, isFalse);
|
||||
expect(legendEntries[0].value, equals(11.0));
|
||||
expect(legendEntries[0].formattedValue, equals('11'));
|
||||
|
||||
expect(legendEntries[1].series, equals(series2));
|
||||
expect(legendEntries[1].label, equals('s2'));
|
||||
expect(legendEntries[1].color, equals(red));
|
||||
expect(legendEntries[1].isSelected, isFalse);
|
||||
expect(legendEntries[1].value, equals(21.0));
|
||||
expect(legendEntries[1].formattedValue, equals('21'));
|
||||
});
|
||||
|
||||
test('series legend - show last measure when there is no selection', () {
|
||||
final seriesList = [series1, series2];
|
||||
final selectionType = SelectionModelType.info;
|
||||
final measureFormatter = (num value) => '${value?.toStringAsFixed(0)}';
|
||||
final legend = new SeriesLegend<String>(
|
||||
selectionModelType: selectionType,
|
||||
legendDefaultMeasure: LegendDefaultMeasure.lastValue,
|
||||
measureFormatter: measureFormatter);
|
||||
|
||||
chart = new ConcreteChart(seriesList);
|
||||
legend.attachTo(chart);
|
||||
chart.callOnDraw();
|
||||
chart.callOnPreProcess();
|
||||
chart.callOnPostProcess();
|
||||
|
||||
final legendEntries = legend.legendState.legendEntries;
|
||||
expect(legendEntries, hasLength(2));
|
||||
expect(legendEntries[0].series, equals(series1));
|
||||
expect(legendEntries[0].label, equals('s1'));
|
||||
expect(legendEntries[0].color, equals(blue));
|
||||
expect(legendEntries[0].isSelected, isFalse);
|
||||
expect(legendEntries[0].value, equals(13.0));
|
||||
expect(legendEntries[0].formattedValue, equals('13'));
|
||||
|
||||
expect(legendEntries[1].series, equals(series2));
|
||||
expect(legendEntries[1].label, equals('s2'));
|
||||
expect(legendEntries[1].color, equals(red));
|
||||
expect(legendEntries[1].isSelected, isFalse);
|
||||
expect(legendEntries[1].value, equals(23.0));
|
||||
expect(legendEntries[1].formattedValue, equals('23'));
|
||||
});
|
||||
}
|
||||
|
||||
class MyRow {
|
||||
final String campaign;
|
||||
final int count;
|
||||
MyRow(this.campaign, this.count);
|
||||
}
|
||||
@@ -0,0 +1,611 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:charts_common/src/chart/cartesian/cartesian_chart.dart';
|
||||
import 'package:charts_common/src/chart/cartesian/axis/axis.dart';
|
||||
import 'package:charts_common/src/chart/common/base_chart.dart';
|
||||
import 'package:charts_common/src/chart/common/datum_details.dart';
|
||||
import 'package:charts_common/src/chart/common/processed_series.dart';
|
||||
import 'package:charts_common/src/chart/common/behavior/slider/slider.dart';
|
||||
import 'package:charts_common/src/chart/common/behavior/selection/selection_trigger.dart';
|
||||
import 'package:charts_common/src/common/gesture_listener.dart';
|
||||
import 'package:charts_common/src/data/series.dart';
|
||||
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class MockChart extends Mock implements CartesianChart {
|
||||
GestureListener lastGestureListener;
|
||||
|
||||
LifecycleListener lastLifecycleListener;
|
||||
|
||||
bool vertical = true;
|
||||
|
||||
@override
|
||||
GestureListener addGestureListener(GestureListener listener) {
|
||||
lastGestureListener = listener;
|
||||
return listener;
|
||||
}
|
||||
|
||||
@override
|
||||
void removeGestureListener(GestureListener listener) {
|
||||
expect(listener, equals(lastGestureListener));
|
||||
lastGestureListener = null;
|
||||
}
|
||||
|
||||
@override
|
||||
addLifecycleListener(LifecycleListener listener) =>
|
||||
lastLifecycleListener = listener;
|
||||
|
||||
@override
|
||||
removeLifecycleListener(LifecycleListener listener) {
|
||||
expect(listener, equals(lastLifecycleListener));
|
||||
lastLifecycleListener = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class MockDomainAxis extends Mock implements NumericAxis {
|
||||
@override
|
||||
double getDomain(num location) {
|
||||
return (location / 20.0).toDouble();
|
||||
}
|
||||
|
||||
@override
|
||||
double getLocation(num domain) {
|
||||
return (domain * 20.0).toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
MockChart _chart;
|
||||
MockDomainAxis _domainAxis;
|
||||
ImmutableSeries _series1;
|
||||
DatumDetails _details1;
|
||||
DatumDetails _details2;
|
||||
DatumDetails _details3;
|
||||
|
||||
SliderTester tester;
|
||||
|
||||
Slider _makeBehavior(SelectionTrigger eventTrigger,
|
||||
{Point<double> handleOffset,
|
||||
Rectangle<int> handleSize,
|
||||
double initialDomainValue,
|
||||
SliderListenerCallback onChangeCallback,
|
||||
bool snapToDatum = false,
|
||||
SliderHandlePosition handlePosition = SliderHandlePosition.middle}) {
|
||||
Slider behavior = new Slider(
|
||||
eventTrigger: eventTrigger,
|
||||
initialDomainValue: initialDomainValue,
|
||||
onChangeCallback: onChangeCallback,
|
||||
snapToDatum: snapToDatum,
|
||||
style: new SliderStyle(
|
||||
handleOffset: handleOffset, handlePosition: handlePosition));
|
||||
|
||||
behavior.attachTo(_chart);
|
||||
|
||||
tester = new SliderTester(behavior);
|
||||
|
||||
// Mock out chart layout by assigning bounds to the layout view.
|
||||
tester.layout(
|
||||
new Rectangle<int>(0, 0, 200, 200), new Rectangle<int>(0, 0, 200, 200));
|
||||
|
||||
return behavior;
|
||||
}
|
||||
|
||||
_setupChart(
|
||||
{Point<double> forPoint,
|
||||
bool isWithinRenderer,
|
||||
List<DatumDetails> respondWithDetails}) {
|
||||
when(_chart.domainAxis).thenReturn(_domainAxis);
|
||||
|
||||
if (isWithinRenderer != null) {
|
||||
when(_chart.pointWithinRenderer(forPoint)).thenReturn(isWithinRenderer);
|
||||
}
|
||||
if (respondWithDetails != null) {
|
||||
when(_chart.getNearestDatumDetailPerSeries(forPoint, true))
|
||||
.thenReturn(respondWithDetails);
|
||||
}
|
||||
}
|
||||
|
||||
setUp(() {
|
||||
_chart = new MockChart();
|
||||
|
||||
_domainAxis = new MockDomainAxis();
|
||||
|
||||
_series1 = new MutableSeries(new Series(
|
||||
id: 'mySeries1',
|
||||
data: [],
|
||||
domainFn: (_, __) {},
|
||||
measureFn: (_, __) {}));
|
||||
|
||||
_details1 = new DatumDetails(
|
||||
chartPosition: new Point(20.0, 80.0),
|
||||
datum: 'myDatum1',
|
||||
domain: 1.0,
|
||||
series: _series1,
|
||||
domainDistance: 10.0,
|
||||
measureDistance: 20.0);
|
||||
_details2 = new DatumDetails(
|
||||
chartPosition: new Point(40.0, 80.0),
|
||||
datum: 'myDatum2',
|
||||
domain: 2.0,
|
||||
series: _series1,
|
||||
domainDistance: 10.0,
|
||||
measureDistance: 20.0);
|
||||
_details3 = new DatumDetails(
|
||||
chartPosition: new Point(90.0, 80.0),
|
||||
datum: 'myDatum3',
|
||||
domain: 4.5,
|
||||
series: _series1,
|
||||
domainDistance: 10.0,
|
||||
measureDistance: 20.0);
|
||||
});
|
||||
|
||||
group('Slider trigger handling', () {
|
||||
test('can listen to tap and drag', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionTrigger.tapAndDrag,
|
||||
handleOffset: new Point<double>(0.0, 0.0),
|
||||
handleSize: new Rectangle<int>(0, 0, 10, 20));
|
||||
|
||||
Point<double> startPoint = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: startPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1]);
|
||||
|
||||
Point<double> updatePoint1 = new Point(50.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint1,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details2]);
|
||||
|
||||
Point<double> updatePoint2 = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint2,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3]);
|
||||
|
||||
Point<double> endPoint = new Point(120.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: endPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3]);
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onAxisConfigured();
|
||||
|
||||
_chart.lastGestureListener.onTapTest(startPoint);
|
||||
_chart.lastGestureListener.onTap(startPoint);
|
||||
|
||||
// Start the drag.
|
||||
_chart.lastGestureListener.onDragStart(startPoint);
|
||||
expect(tester.domainCenterPoint, equals(startPoint));
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, 90, 10, 20)));
|
||||
|
||||
// Drag to first update point.
|
||||
_chart.lastGestureListener.onDragUpdate(updatePoint1, 1.0);
|
||||
expect(tester.domainCenterPoint, equals(updatePoint1));
|
||||
expect(tester.domainValue, equals(2.5));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(45, 90, 10, 20)));
|
||||
|
||||
// Drag to first update point.
|
||||
_chart.lastGestureListener.onDragUpdate(updatePoint2, 1.0);
|
||||
expect(tester.domainCenterPoint, equals(updatePoint2));
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, 90, 10, 20)));
|
||||
|
||||
// Drag the point to the end point.
|
||||
_chart.lastGestureListener.onDragUpdate(endPoint, 1.0);
|
||||
expect(tester.domainCenterPoint, equals(endPoint));
|
||||
expect(tester.domainValue, equals(6.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(115, 90, 10, 20)));
|
||||
|
||||
// Simulate onDragEnd.
|
||||
_chart.lastGestureListener.onDragEnd(endPoint, 1.0, 1.0);
|
||||
|
||||
expect(tester.domainCenterPoint, equals(endPoint));
|
||||
expect(tester.domainValue, equals(6.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(115, 90, 10, 20)));
|
||||
});
|
||||
|
||||
test('slider handle can render at top', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionTrigger.tapAndDrag,
|
||||
handleOffset: new Point<double>(0.0, 0.0),
|
||||
handleSize: new Rectangle<int>(0, 0, 10, 20),
|
||||
handlePosition: SliderHandlePosition.top);
|
||||
|
||||
Point<double> startPoint = new Point(100.0, 0.0);
|
||||
_setupChart(
|
||||
forPoint: startPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1]);
|
||||
|
||||
Point<double> updatePoint1 = new Point(50.0, 0.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint1,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details2]);
|
||||
|
||||
Point<double> updatePoint2 = new Point(100.0, 0.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint2,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3]);
|
||||
|
||||
Point<double> endPoint = new Point(120.0, 0.0);
|
||||
_setupChart(
|
||||
forPoint: endPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3]);
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onAxisConfigured();
|
||||
|
||||
_chart.lastGestureListener.onTapTest(startPoint);
|
||||
_chart.lastGestureListener.onTap(startPoint);
|
||||
|
||||
// Start the drag.
|
||||
_chart.lastGestureListener.onDragStart(startPoint);
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, -10, 10, 20)));
|
||||
|
||||
// Drag to first update point.
|
||||
_chart.lastGestureListener.onDragUpdate(updatePoint1, 1.0);
|
||||
expect(tester.domainValue, equals(2.5));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(45, -10, 10, 20)));
|
||||
|
||||
// Drag to first update point.
|
||||
_chart.lastGestureListener.onDragUpdate(updatePoint2, 1.0);
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, -10, 10, 20)));
|
||||
|
||||
// Drag the point to the end point.
|
||||
_chart.lastGestureListener.onDragUpdate(endPoint, 1.0);
|
||||
expect(tester.domainValue, equals(6.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(115, -10, 10, 20)));
|
||||
|
||||
// Simulate onDragEnd.
|
||||
_chart.lastGestureListener.onDragEnd(endPoint, 1.0, 1.0);
|
||||
|
||||
expect(tester.domainValue, equals(6.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(115, -10, 10, 20)));
|
||||
});
|
||||
|
||||
test('can listen to press hold', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionTrigger.pressHold,
|
||||
handleOffset: new Point<double>(0.0, 0.0),
|
||||
handleSize: new Rectangle<int>(0, 0, 10, 20));
|
||||
|
||||
Point<double> startPoint = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: startPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1]);
|
||||
|
||||
Point<double> updatePoint1 = new Point(50.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint1,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details2]);
|
||||
|
||||
Point<double> updatePoint2 = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint2,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3]);
|
||||
|
||||
Point<double> endPoint = new Point(120.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: endPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3]);
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onAxisConfigured();
|
||||
|
||||
_chart.lastGestureListener.onTapTest(startPoint);
|
||||
_chart.lastGestureListener.onLongPress(startPoint);
|
||||
|
||||
// Start the drag.
|
||||
_chart.lastGestureListener.onDragStart(startPoint);
|
||||
expect(tester.domainCenterPoint, equals(startPoint));
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, 90, 10, 20)));
|
||||
|
||||
// Drag to first update point.
|
||||
_chart.lastGestureListener.onDragUpdate(updatePoint1, 1.0);
|
||||
expect(tester.domainCenterPoint, equals(updatePoint1));
|
||||
expect(tester.domainValue, equals(2.5));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(45, 90, 10, 20)));
|
||||
|
||||
// Drag to first update point.
|
||||
_chart.lastGestureListener.onDragUpdate(updatePoint2, 1.0);
|
||||
expect(tester.domainCenterPoint, equals(updatePoint2));
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, 90, 10, 20)));
|
||||
|
||||
// Drag the point to the end point.
|
||||
_chart.lastGestureListener.onDragUpdate(endPoint, 1.0);
|
||||
expect(tester.domainCenterPoint, equals(endPoint));
|
||||
expect(tester.domainValue, equals(6.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(115, 90, 10, 20)));
|
||||
|
||||
// Simulate onDragEnd.
|
||||
_chart.lastGestureListener.onDragEnd(endPoint, 1.0, 1.0);
|
||||
|
||||
expect(tester.domainCenterPoint, equals(endPoint));
|
||||
expect(tester.domainValue, equals(6.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(115, 90, 10, 20)));
|
||||
});
|
||||
|
||||
test('can listen to long press hold', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionTrigger.longPressHold,
|
||||
handleOffset: new Point<double>(0.0, 0.0),
|
||||
handleSize: new Rectangle<int>(0, 0, 10, 20));
|
||||
|
||||
Point<double> startPoint = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: startPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1]);
|
||||
|
||||
Point<double> updatePoint1 = new Point(50.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint1,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details2]);
|
||||
|
||||
Point<double> updatePoint2 = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint2,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3]);
|
||||
|
||||
Point<double> endPoint = new Point(120.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: endPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3]);
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onAxisConfigured();
|
||||
|
||||
_chart.lastGestureListener.onTapTest(startPoint);
|
||||
_chart.lastGestureListener.onLongPress(startPoint);
|
||||
|
||||
// Start the drag.
|
||||
_chart.lastGestureListener.onDragStart(startPoint);
|
||||
expect(tester.domainCenterPoint, equals(startPoint));
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, 90, 10, 20)));
|
||||
|
||||
// Drag to first update point.
|
||||
_chart.lastGestureListener.onDragUpdate(updatePoint1, 1.0);
|
||||
expect(tester.domainCenterPoint, equals(updatePoint1));
|
||||
expect(tester.domainValue, equals(2.5));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(45, 90, 10, 20)));
|
||||
|
||||
// Drag to first update point.
|
||||
_chart.lastGestureListener.onDragUpdate(updatePoint2, 1.0);
|
||||
expect(tester.domainCenterPoint, equals(updatePoint2));
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, 90, 10, 20)));
|
||||
|
||||
// Drag the point to the end point.
|
||||
_chart.lastGestureListener.onDragUpdate(endPoint, 1.0);
|
||||
expect(tester.domainCenterPoint, equals(endPoint));
|
||||
expect(tester.domainValue, equals(6.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(115, 90, 10, 20)));
|
||||
|
||||
// Simulate onDragEnd.
|
||||
_chart.lastGestureListener.onDragEnd(endPoint, 1.0, 1.0);
|
||||
|
||||
expect(tester.domainCenterPoint, equals(endPoint));
|
||||
expect(tester.domainValue, equals(6.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(115, 90, 10, 20)));
|
||||
});
|
||||
|
||||
test('no position update before long press', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionTrigger.longPressHold,
|
||||
handleOffset: new Point<double>(0.0, 0.0),
|
||||
handleSize: new Rectangle<int>(0, 0, 10, 20));
|
||||
|
||||
Point<double> startPoint = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: startPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1]);
|
||||
|
||||
Point<double> updatePoint1 = new Point(50.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint1,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details2]);
|
||||
|
||||
Point<double> updatePoint2 = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint2,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3]);
|
||||
|
||||
Point<double> endPoint = new Point(120.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: endPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3]);
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onAxisConfigured();
|
||||
|
||||
_chart.lastGestureListener.onTapTest(startPoint);
|
||||
|
||||
// Start the drag.
|
||||
_chart.lastGestureListener.onDragStart(startPoint);
|
||||
expect(tester.domainCenterPoint, equals(startPoint));
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, 90, 10, 20)));
|
||||
|
||||
// Drag the point to the end point.
|
||||
_chart.lastGestureListener.onDragUpdate(endPoint, 1.0);
|
||||
expect(tester.domainCenterPoint, equals(startPoint));
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, 90, 10, 20)));
|
||||
|
||||
// Simulate onDragEnd.
|
||||
_chart.lastGestureListener.onDragEnd(endPoint, 1.0, 1.0);
|
||||
|
||||
expect(tester.domainCenterPoint, equals(startPoint));
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, 90, 10, 20)));
|
||||
});
|
||||
|
||||
test('can snap to datum', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
_makeBehavior(SelectionTrigger.tapAndDrag,
|
||||
handleOffset: new Point<double>(0.0, 0.0),
|
||||
handleSize: new Rectangle<int>(0, 0, 10, 20),
|
||||
snapToDatum: true);
|
||||
|
||||
Point<double> startPoint = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: startPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1]);
|
||||
|
||||
Point<double> updatePoint1 = new Point(50.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint1,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details2]);
|
||||
|
||||
Point<double> updatePoint2 = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: updatePoint2,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3]);
|
||||
|
||||
Point<double> endPoint = new Point(120.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: endPoint,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details3]);
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onAxisConfigured();
|
||||
|
||||
_chart.lastGestureListener.onTapTest(startPoint);
|
||||
_chart.lastGestureListener.onTap(startPoint);
|
||||
|
||||
// Start the drag.
|
||||
_chart.lastGestureListener.onDragStart(startPoint);
|
||||
expect(tester.domainCenterPoint, equals(startPoint));
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, 90, 10, 20)));
|
||||
|
||||
// Drag to first update point. The slider should follow the mouse during
|
||||
// each drag update.
|
||||
_chart.lastGestureListener.onDragUpdate(updatePoint1, 1.0);
|
||||
expect(tester.domainCenterPoint, equals(updatePoint1));
|
||||
expect(tester.domainValue, equals(2.5));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(45, 90, 10, 20)));
|
||||
|
||||
// Drag to first update point.
|
||||
_chart.lastGestureListener.onDragUpdate(updatePoint2, 1.0);
|
||||
expect(tester.domainCenterPoint, equals(updatePoint2));
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, 90, 10, 20)));
|
||||
|
||||
// Drag the point to the end point.
|
||||
_chart.lastGestureListener.onDragUpdate(endPoint, 1.0);
|
||||
expect(tester.domainCenterPoint, equals(endPoint));
|
||||
expect(tester.domainValue, equals(6.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(115, 90, 10, 20)));
|
||||
|
||||
// Simulate onDragEnd. This is where we expect the snap to occur.
|
||||
_chart.lastGestureListener.onDragEnd(endPoint, 1.0, 1.0);
|
||||
|
||||
expect(tester.domainCenterPoint, equals(new Point<int>(90, 100)));
|
||||
expect(tester.domainValue, equals(4.5));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(85, 90, 10, 20)));
|
||||
});
|
||||
});
|
||||
|
||||
group('Slider manual control', () {
|
||||
test('can set domain position', () {
|
||||
// Setup chart matches point with single domain single series.
|
||||
final slider = _makeBehavior(SelectionTrigger.tapAndDrag,
|
||||
handleOffset: new Point<double>(0.0, 0.0),
|
||||
handleSize: new Rectangle<int>(0, 0, 10, 20),
|
||||
initialDomainValue: 1.0);
|
||||
|
||||
_setupChart();
|
||||
|
||||
// Act
|
||||
_chart.lastLifecycleListener.onAxisConfigured();
|
||||
|
||||
// Verify initial position.
|
||||
expect(tester.domainCenterPoint, equals(new Point(20.0, 100.0)));
|
||||
expect(tester.domainValue, equals(1.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(15, 90, 10, 20)));
|
||||
|
||||
// Move to first domain value.
|
||||
slider.moveSliderToDomain(2);
|
||||
expect(tester.domainCenterPoint, equals(new Point(40.0, 100.0)));
|
||||
expect(tester.domainValue, equals(2.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(35, 90, 10, 20)));
|
||||
|
||||
// Move to second domain value.
|
||||
slider.moveSliderToDomain(5);
|
||||
expect(tester.domainCenterPoint, equals(new Point(100.0, 100.0)));
|
||||
expect(tester.domainValue, equals(5.0));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(95, 90, 10, 20)));
|
||||
|
||||
// Move to second domain value.
|
||||
slider.moveSliderToDomain(7.5);
|
||||
expect(tester.domainCenterPoint, equals(new Point(150.0, 100.0)));
|
||||
expect(tester.domainValue, equals(7.5));
|
||||
expect(tester.handleBounds, equals(new Rectangle<int>(145, 90, 10, 20)));
|
||||
});
|
||||
});
|
||||
|
||||
group('Cleanup', () {
|
||||
test('detach removes listener', () {
|
||||
// Setup
|
||||
Slider behavior = _makeBehavior(SelectionTrigger.tapAndDrag);
|
||||
|
||||
Point<double> point = new Point(100.0, 100.0);
|
||||
_setupChart(
|
||||
forPoint: point,
|
||||
isWithinRenderer: true,
|
||||
respondWithDetails: [_details1]);
|
||||
expect(_chart.lastGestureListener, isNotNull);
|
||||
|
||||
// Act
|
||||
behavior.removeFrom(_chart);
|
||||
|
||||
// Validate
|
||||
expect(_chart.lastGestureListener, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
249
web/charts/common/test/chart/common/gesture_listener_test.dart
Normal file
249
web/charts/common/test/chart/common/gesture_listener_test.dart
Normal file
@@ -0,0 +1,249 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'dart:math' show Point;
|
||||
import 'package:charts_common/src/common/gesture_listener.dart';
|
||||
import 'package:charts_common/src/common/proxy_gesture_listener.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
ProxyGestureListener _proxy;
|
||||
Point<double> _point;
|
||||
setUp(() {
|
||||
_proxy = new ProxyGestureListener();
|
||||
_point = new Point<double>(10.0, 12.0);
|
||||
});
|
||||
|
||||
group('Tap gesture', () {
|
||||
test('notified for simple case', () {
|
||||
// Setup
|
||||
final tapListener = new MockListener(consumeEvent: true);
|
||||
_proxy.add(new GestureListener(onTap: tapListener.callback));
|
||||
|
||||
// Act
|
||||
_proxy.onTapTest(_point);
|
||||
_proxy.onTap(_point);
|
||||
|
||||
// Verify
|
||||
tapListener.verify(arg1: _point);
|
||||
});
|
||||
|
||||
test('notifies new listener for second event', () {
|
||||
// Setup
|
||||
final tapListener1 = new MockListener();
|
||||
_proxy.add(new GestureListener(
|
||||
onTap: tapListener1.callback,
|
||||
));
|
||||
|
||||
// Act
|
||||
_proxy.onTapTest(_point);
|
||||
_proxy.onTap(_point);
|
||||
|
||||
// Verify
|
||||
tapListener1.verify(arg1: _point);
|
||||
|
||||
// Setup Another
|
||||
final tapListener2 = new MockListener();
|
||||
_proxy.add(new GestureListener(
|
||||
onTap: tapListener2.callback,
|
||||
));
|
||||
|
||||
// Act
|
||||
_proxy.onTapTest(_point);
|
||||
_proxy.onTap(_point);
|
||||
|
||||
// Verify
|
||||
tapListener1.verify(callCount: 2, arg1: _point);
|
||||
tapListener2.verify(arg1: _point);
|
||||
});
|
||||
|
||||
test('notifies claiming listener registered first', () {
|
||||
// Setup
|
||||
final claimingTapDownListener = new MockListener(consumeEvent: true);
|
||||
final claimingTapListener = new MockListener(consumeEvent: true);
|
||||
|
||||
_proxy.add(new GestureListener(
|
||||
onTapTest: claimingTapDownListener.callback,
|
||||
onTap: claimingTapListener.callback,
|
||||
));
|
||||
|
||||
final nonclaimingTapDownListener = new MockListener(consumeEvent: false);
|
||||
final nonclaimingTapListener = new MockListener(consumeEvent: false);
|
||||
|
||||
_proxy.add(new GestureListener(
|
||||
onTapTest: nonclaimingTapDownListener.callback,
|
||||
onTap: nonclaimingTapListener.callback,
|
||||
));
|
||||
|
||||
// Act
|
||||
_proxy.onTapTest(_point);
|
||||
_proxy.onTap(_point);
|
||||
|
||||
// Verify
|
||||
claimingTapDownListener.verify(arg1: _point);
|
||||
claimingTapListener.verify(arg1: _point);
|
||||
nonclaimingTapDownListener.verify(arg1: _point);
|
||||
nonclaimingTapListener.verify(callCount: 0);
|
||||
});
|
||||
|
||||
test('notifies claiming listener registered second', () {
|
||||
// Setup
|
||||
final nonclaimingTapDownListener = new MockListener(consumeEvent: false);
|
||||
final nonclaimingTapListener = new MockListener(consumeEvent: false);
|
||||
|
||||
_proxy.add(new GestureListener(
|
||||
onTapTest: nonclaimingTapDownListener.callback,
|
||||
onTap: nonclaimingTapListener.callback,
|
||||
));
|
||||
|
||||
final claimingTapDownListener = new MockListener(consumeEvent: true);
|
||||
final claimingTapListener = new MockListener(consumeEvent: true);
|
||||
|
||||
_proxy.add(new GestureListener(
|
||||
onTapTest: claimingTapDownListener.callback,
|
||||
onTap: claimingTapListener.callback,
|
||||
));
|
||||
|
||||
// Act
|
||||
_proxy.onTapTest(_point);
|
||||
_proxy.onTap(_point);
|
||||
|
||||
// Verify
|
||||
nonclaimingTapDownListener.verify(arg1: _point);
|
||||
nonclaimingTapListener.verify(callCount: 0);
|
||||
claimingTapDownListener.verify(arg1: _point);
|
||||
claimingTapListener.verify(arg1: _point);
|
||||
});
|
||||
});
|
||||
|
||||
group('LongPress gesture', () {
|
||||
test('notifies with tap', () {
|
||||
// Setup
|
||||
final tapDown = new MockListener(consumeEvent: true);
|
||||
final tap = new MockListener(consumeEvent: true);
|
||||
final tapCancel = new MockListener(consumeEvent: true);
|
||||
|
||||
_proxy.add(new GestureListener(
|
||||
onTapTest: tapDown.callback,
|
||||
onTap: tap.callback,
|
||||
onTapCancel: tapCancel.callback,
|
||||
));
|
||||
|
||||
final pressTapDown = new MockListener(consumeEvent: true);
|
||||
final longPress = new MockListener(consumeEvent: true);
|
||||
final pressCancel = new MockListener(consumeEvent: true);
|
||||
|
||||
_proxy.add(new GestureListener(
|
||||
onTapTest: pressTapDown.callback,
|
||||
onLongPress: longPress.callback,
|
||||
onTapCancel: pressCancel.callback,
|
||||
));
|
||||
|
||||
// Act
|
||||
_proxy.onTapTest(_point);
|
||||
_proxy.onLongPress(_point);
|
||||
_proxy.onTap(_point);
|
||||
|
||||
// Verify
|
||||
tapDown.verify(arg1: _point);
|
||||
tap.verify(callCount: 0);
|
||||
tapCancel.verify(callCount: 1);
|
||||
|
||||
pressTapDown.verify(arg1: _point);
|
||||
longPress.verify(arg1: _point);
|
||||
pressCancel.verify(callCount: 0);
|
||||
});
|
||||
});
|
||||
|
||||
group('Drag gesture', () {
|
||||
test('wins over tap', () {
|
||||
// Setup
|
||||
final tapDown = new MockListener(consumeEvent: true);
|
||||
final tap = new MockListener(consumeEvent: true);
|
||||
final tapCancel = new MockListener(consumeEvent: true);
|
||||
|
||||
_proxy.add(new GestureListener(
|
||||
onTapTest: tapDown.callback,
|
||||
onTap: tap.callback,
|
||||
onTapCancel: tapCancel.callback,
|
||||
));
|
||||
|
||||
final dragTapDown = new MockListener(consumeEvent: true);
|
||||
final dragStart = new MockListener(consumeEvent: true);
|
||||
final dragUpdate = new MockListener(consumeEvent: true);
|
||||
final dragEnd = new MockListener(consumeEvent: true);
|
||||
final dragCancel = new MockListener(consumeEvent: true);
|
||||
|
||||
_proxy.add(new GestureListener(
|
||||
onTapTest: dragTapDown.callback,
|
||||
onDragStart: dragStart.callback,
|
||||
onDragUpdate: dragUpdate.callback,
|
||||
onDragEnd: dragEnd.callback,
|
||||
onTapCancel: dragCancel.callback,
|
||||
));
|
||||
|
||||
// Act
|
||||
_proxy.onTapTest(_point);
|
||||
_proxy.onDragStart(_point);
|
||||
_proxy.onDragUpdate(_point, 1.0);
|
||||
_proxy.onDragUpdate(_point, 1.0);
|
||||
_proxy.onDragEnd(_point, 2.0, 3.0);
|
||||
_proxy.onTap(_point);
|
||||
|
||||
// Verify
|
||||
tapDown.verify(arg1: _point);
|
||||
tap.verify(callCount: 0);
|
||||
tapCancel.verify(callCount: 1);
|
||||
|
||||
dragTapDown.verify(arg1: _point);
|
||||
dragStart.verify(arg1: _point);
|
||||
dragUpdate.verify(callCount: 2, arg1: _point, arg2: 1.0);
|
||||
dragEnd.verify(arg1: _point, arg2: 2.0, arg3: 3.0);
|
||||
dragCancel.verify(callCount: 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MockListener {
|
||||
Object _arg1;
|
||||
Object _arg2;
|
||||
Object _arg3;
|
||||
int _callCount = 0;
|
||||
|
||||
final bool consumeEvent;
|
||||
|
||||
MockListener({this.consumeEvent = false});
|
||||
|
||||
bool callback([Object arg1, Object arg2, Object arg3]) {
|
||||
_arg1 = arg1;
|
||||
_arg2 = arg2;
|
||||
_arg3 = arg3;
|
||||
|
||||
_callCount++;
|
||||
|
||||
return consumeEvent;
|
||||
}
|
||||
|
||||
verify({int callCount = 1, Object arg1, Object arg2, Object arg3}) {
|
||||
if (callCount != any) {
|
||||
expect(_callCount, equals(callCount));
|
||||
}
|
||||
expect(_arg1, equals(arg1));
|
||||
expect(_arg2, equals(arg2));
|
||||
expect(_arg3, equals(arg3));
|
||||
}
|
||||
}
|
||||
|
||||
const any = -1;
|
||||
@@ -0,0 +1,331 @@
|
||||
// Copyright 2018 the Charts project authors. Please see the AUTHORS file
|
||||
// for details.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import 'package:charts_common/src/chart/common/selection_model/selection_model.dart';
|
||||
import 'package:charts_common/src/chart/common/processed_series.dart';
|
||||
import 'package:charts_common/src/chart/common/series_datum.dart';
|
||||
import 'package:charts_common/src/data/series.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
MutableSelectionModel<String> _selectionModel;
|
||||
|
||||
ImmutableSeries<String> _closestSeries;
|
||||
MyDatum _closestDatumClosestSeries;
|
||||
SeriesDatum<String> _closestDatumClosestSeriesPair;
|
||||
MyDatum _otherDatumClosestSeries;
|
||||
SeriesDatum<String> _otherDatumClosestSeriesPair;
|
||||
|
||||
ImmutableSeries<String> _otherSeries;
|
||||
MyDatum _closestDatumOtherSeries;
|
||||
SeriesDatum<String> _closestDatumOtherSeriesPair;
|
||||
MyDatum _otherDatumOtherSeries;
|
||||
SeriesDatum<String> _otherDatumOtherSeriesPair;
|
||||
|
||||
setUp(() {
|
||||
_selectionModel = new MutableSelectionModel<String>();
|
||||
|
||||
_closestDatumClosestSeries = new MyDatum('cDcS');
|
||||
_otherDatumClosestSeries = new MyDatum('oDcS');
|
||||
_closestSeries = new MutableSeries<String>(new Series<MyDatum, String>(
|
||||
id: 'closest',
|
||||
data: [_closestDatumClosestSeries, _otherDatumClosestSeries],
|
||||
domainFn: (dynamic d, _) => d.id,
|
||||
measureFn: (_, __) => 0));
|
||||
_closestDatumClosestSeriesPair =
|
||||
new SeriesDatum<String>(_closestSeries, _closestDatumClosestSeries);
|
||||
_otherDatumClosestSeriesPair =
|
||||
new SeriesDatum<String>(_closestSeries, _otherDatumClosestSeries);
|
||||
|
||||
_closestDatumOtherSeries = new MyDatum('cDoS');
|
||||
_otherDatumOtherSeries = new MyDatum('oDoS');
|
||||
_otherSeries = new MutableSeries<String>(new Series<MyDatum, String>(
|
||||
id: 'other',
|
||||
data: [_closestDatumOtherSeries, _otherDatumOtherSeries],
|
||||
domainFn: (dynamic d, _) => d.id,
|
||||
measureFn: (_, __) => 0));
|
||||
_closestDatumOtherSeriesPair =
|
||||
new SeriesDatum<String>(_otherSeries, _closestDatumOtherSeries);
|
||||
_otherDatumOtherSeriesPair =
|
||||
new SeriesDatum<String>(_otherSeries, _otherDatumOtherSeries);
|
||||
});
|
||||
|
||||
group('SelectionModel persists values', () {
|
||||
test('selection model is empty by default', () {
|
||||
expect(_selectionModel.hasDatumSelection, isFalse);
|
||||
expect(_selectionModel.hasSeriesSelection, isFalse);
|
||||
});
|
||||
|
||||
test('all datum are selected but only the first Series is', () {
|
||||
// Select the 'closest' datum for each Series.
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_closestSeries, _closestDatumClosestSeries),
|
||||
new SeriesDatum(_otherSeries, _closestDatumOtherSeries),
|
||||
], [
|
||||
_closestSeries
|
||||
]);
|
||||
|
||||
expect(_selectionModel.hasDatumSelection, isTrue);
|
||||
expect(_selectionModel.selectedDatum, hasLength(2));
|
||||
expect(_selectionModel.selectedDatum,
|
||||
contains(_closestDatumClosestSeriesPair));
|
||||
expect(_selectionModel.selectedDatum,
|
||||
contains(_closestDatumOtherSeriesPair));
|
||||
expect(
|
||||
_selectionModel.selectedDatum.contains(_otherDatumClosestSeriesPair),
|
||||
isFalse);
|
||||
expect(_selectionModel.selectedDatum.contains(_otherDatumOtherSeriesPair),
|
||||
isFalse);
|
||||
|
||||
expect(_selectionModel.hasSeriesSelection, isTrue);
|
||||
expect(_selectionModel.selectedSeries, hasLength(1));
|
||||
expect(_selectionModel.selectedSeries, contains(_closestSeries));
|
||||
expect(_selectionModel.selectedSeries.contains(_otherSeries), isFalse);
|
||||
});
|
||||
|
||||
test('selection can change', () {
|
||||
// Select the 'closest' datum for each Series.
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_closestSeries, _closestDatumClosestSeries),
|
||||
new SeriesDatum(_otherSeries, _closestDatumOtherSeries),
|
||||
], [
|
||||
_closestSeries
|
||||
]);
|
||||
|
||||
// Change selection to just the other datum on the other series.
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_otherSeries, _otherDatumOtherSeries),
|
||||
], [
|
||||
_otherSeries
|
||||
]);
|
||||
|
||||
expect(_selectionModel.selectedDatum, hasLength(1));
|
||||
expect(
|
||||
_selectionModel.selectedDatum, contains(_otherDatumOtherSeriesPair));
|
||||
|
||||
expect(_selectionModel.selectedSeries, hasLength(1));
|
||||
expect(_selectionModel.selectedSeries, contains(_otherSeries));
|
||||
});
|
||||
|
||||
test('selection can be series only', () {
|
||||
// Select the 'closest' Series without datum to simulate legend hovering.
|
||||
_selectionModel.updateSelection([], [_closestSeries]);
|
||||
|
||||
expect(_selectionModel.hasDatumSelection, isFalse);
|
||||
expect(_selectionModel.selectedDatum, hasLength(0));
|
||||
|
||||
expect(_selectionModel.hasSeriesSelection, isTrue);
|
||||
expect(_selectionModel.selectedSeries, hasLength(1));
|
||||
expect(_selectionModel.selectedSeries, contains(_closestSeries));
|
||||
});
|
||||
|
||||
test('selection lock prevents change', () {
|
||||
// Prevent selection changes.
|
||||
_selectionModel.locked = true;
|
||||
|
||||
// Try to the 'closest' datum for each Series.
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_closestSeries, _closestDatumClosestSeries),
|
||||
new SeriesDatum(_otherSeries, _closestDatumOtherSeries),
|
||||
], [
|
||||
_closestSeries
|
||||
]);
|
||||
|
||||
expect(_selectionModel.hasDatumSelection, isFalse);
|
||||
expect(_selectionModel.hasSeriesSelection, isFalse);
|
||||
|
||||
// Allow selection changes.
|
||||
_selectionModel.locked = false;
|
||||
|
||||
// Try to the 'closest' datum for each Series.
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_closestSeries, _closestDatumClosestSeries),
|
||||
new SeriesDatum(_otherSeries, _closestDatumOtherSeries),
|
||||
], [
|
||||
_closestSeries
|
||||
]);
|
||||
|
||||
expect(_selectionModel.hasDatumSelection, isTrue);
|
||||
expect(_selectionModel.hasSeriesSelection, isTrue);
|
||||
|
||||
// Prevent selection changes.
|
||||
_selectionModel.locked = true;
|
||||
|
||||
// Attempt to change selection
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_otherSeries, _otherDatumOtherSeries),
|
||||
], [
|
||||
_otherSeries
|
||||
]);
|
||||
|
||||
// Previous selection should still be set.
|
||||
expect(_selectionModel.selectedDatum, hasLength(2));
|
||||
expect(_selectionModel.selectedDatum,
|
||||
contains(_closestDatumClosestSeriesPair));
|
||||
expect(_selectionModel.selectedDatum,
|
||||
contains(_closestDatumOtherSeriesPair));
|
||||
|
||||
expect(_selectionModel.selectedSeries, hasLength(1));
|
||||
expect(_selectionModel.selectedSeries, contains(_closestSeries));
|
||||
});
|
||||
});
|
||||
|
||||
group('SelectionModel changed listeners', () {
|
||||
test('listener triggered for change', () {
|
||||
SelectionModel<String> triggeredModel;
|
||||
// Listen
|
||||
_selectionModel
|
||||
.addSelectionChangedListener((SelectionModel<String> model) {
|
||||
triggeredModel = model;
|
||||
});
|
||||
|
||||
// Set the selection to closest datum.
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_closestSeries, _closestDatumClosestSeries),
|
||||
], [
|
||||
_closestSeries
|
||||
]);
|
||||
|
||||
// Callback should have been triggered.
|
||||
expect(triggeredModel, equals(_selectionModel));
|
||||
});
|
||||
|
||||
test('listener not triggered for no change', () {
|
||||
SelectionModel<String> triggeredModel;
|
||||
// Set the selection to closest datum.
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_closestSeries, _closestDatumClosestSeries),
|
||||
], [
|
||||
_closestSeries
|
||||
]);
|
||||
|
||||
// Listen
|
||||
_selectionModel
|
||||
.addSelectionChangedListener((SelectionModel<String> model) {
|
||||
triggeredModel = model;
|
||||
});
|
||||
|
||||
// Try to update the model with the same value.
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_closestSeries, _closestDatumClosestSeries),
|
||||
], [
|
||||
_closestSeries
|
||||
]);
|
||||
|
||||
// Callback should not have been triggered.
|
||||
expect(triggeredModel, isNull);
|
||||
});
|
||||
|
||||
test('removed listener not triggered for change', () {
|
||||
SelectionModel<String> triggeredModel;
|
||||
|
||||
Function cb = (SelectionModel<String> model) {
|
||||
triggeredModel = model;
|
||||
};
|
||||
|
||||
// Listen
|
||||
_selectionModel.addSelectionChangedListener(cb);
|
||||
|
||||
// Unlisten
|
||||
_selectionModel.removeSelectionChangedListener(cb);
|
||||
|
||||
// Set the selection to closest datum.
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_closestSeries, _closestDatumClosestSeries),
|
||||
], [
|
||||
_closestSeries
|
||||
]);
|
||||
|
||||
// Callback should not have been triggered.
|
||||
expect(triggeredModel, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('SelectionModel updated listeners', () {
|
||||
test('listener triggered for change', () {
|
||||
SelectionModel<String> triggeredModel;
|
||||
// Listen
|
||||
_selectionModel
|
||||
.addSelectionUpdatedListener((SelectionModel<String> model) {
|
||||
triggeredModel = model;
|
||||
});
|
||||
|
||||
// Set the selection to closest datum.
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_closestSeries, _closestDatumClosestSeries),
|
||||
], [
|
||||
_closestSeries
|
||||
]);
|
||||
|
||||
// Callback should have been triggered.
|
||||
expect(triggeredModel, equals(_selectionModel));
|
||||
});
|
||||
|
||||
test('listener triggered for no change', () {
|
||||
SelectionModel<String> triggeredModel;
|
||||
// Set the selection to closest datum.
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_closestSeries, _closestDatumClosestSeries),
|
||||
], [
|
||||
_closestSeries
|
||||
]);
|
||||
|
||||
// Listen
|
||||
_selectionModel
|
||||
.addSelectionUpdatedListener((SelectionModel<String> model) {
|
||||
triggeredModel = model;
|
||||
});
|
||||
|
||||
// Try to update the model with the same value.
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_closestSeries, _closestDatumClosestSeries),
|
||||
], [
|
||||
_closestSeries
|
||||
]);
|
||||
|
||||
// Callback should have been triggered.
|
||||
expect(triggeredModel, equals(_selectionModel));
|
||||
});
|
||||
|
||||
test('removed listener not triggered for change', () {
|
||||
SelectionModel<String> triggeredModel;
|
||||
|
||||
Function cb = (SelectionModel<String> model) {
|
||||
triggeredModel = model;
|
||||
};
|
||||
|
||||
// Listen
|
||||
_selectionModel.addSelectionUpdatedListener(cb);
|
||||
|
||||
// Unlisten
|
||||
_selectionModel.removeSelectionUpdatedListener(cb);
|
||||
|
||||
// Set the selection to closest datum.
|
||||
_selectionModel.updateSelection([
|
||||
new SeriesDatum(_closestSeries, _closestDatumClosestSeries),
|
||||
], [
|
||||
_closestSeries
|
||||
]);
|
||||
|
||||
// Callback should not have been triggered.
|
||||
expect(triggeredModel, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MyDatum {
|
||||
final String id;
|
||||
MyDatum(this.id);
|
||||
}
|
||||
Reference in New Issue
Block a user