Update to a Dart 2 compatible version of package:charted. (#7)
diff --git a/packages/charted/README.md b/packages/charted/README.md
index c48b83b..4a3144b 100644
--- a/packages/charted/README.md
+++ b/packages/charted/README.md
@@ -21,5 +21,6 @@
```bash
git clone git@github.com:google/charted.git
cd charted
+pub get
pub serve examples
```
diff --git a/packages/charted/changelog.md b/packages/charted/changelog.md
new file mode 100644
index 0000000..9707402
--- /dev/null
+++ b/packages/charted/changelog.md
@@ -0,0 +1,88 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+This log starts from version 0.5.0. Older versions are omitted, but this should
+be kept up-to-date when a version update happens in the future.
+
+The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
+
+## 0.6.0 - 2018-07-09
+
+
+### Changed
+- Internal changes to make charted Dart 2.0 compatible.
+
+- Several typedef signatures have changed: `CompareFunc`,
+ `InterpolatorGenerator`:
+
+ | | Breaking typedef signature changes |
+ |---------|------------------------------------|
+ | **OLD** | `typedef int CompareFunc(dynamic a, dynamic b);`
+ | **NEW** | `typedef int CompareFunc(Comparable a, Comparable b);`
+ | | |
+ | **OLD** | `typedef Interpolator InterpolatorGenerator(a, b);`
+ | **NEW** | `typedef Interpolator InterpolatorGenerator<T>(T a, T b);`
+ | | |
+
+- Several member signatures have changed: `Extent.items`, `LinearScale.interpolator`,
+ `OrdinalScale.rangePoints`, `OrdinalScale.rangeBands`,
+ `OrdinalScale.rangeRoundBands`, `ScaleUtils.extent`, `TimeFormat.multi`:
+
+ | | Breaking member signature changes |
+ |---------|-----------------------------------|
+ | | **Class `Extent`** |
+ | **OLD** | `factory Extent.items(Iterable<T> items, [Comparator compare = Comparable.compare]);`
+ | **NEW** | `factory Extent.items(Iterable<T> items, [Comparator<T> compare = Comparable.compare]);`
+ | | |
+ | | **Class `LinearScale`** |
+ | **OLD** | `InterpolatorGenerator interpolator;`
+ | **NEW** | `InterpolatorGenerator<num> interpolator;`
+ | | |
+ | | **Class `OrdinalScale`** |
+ | **OLD** | `void rangePoints(Iterable range, [double padding]);`
+ | **NEW** | `void rangePoints(Iterable<num> range, [double padding]);`
+ | | |
+ | **OLD** | `void rangeBands(Iterable range, [double padding, double outerPadding]);`
+ | **NEW** | `void rangeBands(Iterable<num> range, [double padding, double outerPadding]);`
+ | | |
+ | **OLD** | `void rangeRoundBands(Iterable range, [double padding, double outerPadding]);`
+ | **NEW** | `void rangeRoundBands(Iterable<num> range, [double padding, double outerPadding]);`
+ | | |
+ | | **Class `ScaleUtils`** |
+ | **OLD** | `static Extent extent(Iterable<num> values)`
+ | **NEW** | `static Extent<num> extent(Iterable<num> values)`
+ | | |
+ | | **Class `TimeFormat`** |
+ | **OLD** | `TimeFormatFunction multi(List<List> formats)`
+ | **NEW** | `FormatFunction multi(List<List> formats)`
+
+- The `charted.core.utils` library's `sum` function's signature changed:
+
+ | | Breaking library member signature changes |
+ |---------|-------------------------------------------|
+ | | **Library `charted.core.utils`** |
+ | **OLD** | `num sum(List values)`
+ | **NEW** | `num sum(List<num> values)`
+
+- The signature of the `Extent` class changed:
+
+ | | Breaking class signature changes |
+ |---------|----------------------------------|
+ | | **Class `Extent`** |
+ | **OLD** | `class Extent<T> extends Pair<T, T>`
+ | **NEW** | `class Extent<T extends Comparable> extends Pair<T, T>`
+
+## [0.5.0] - 2017-11-16
+
+### Added
+- Stacked line renderer for rendering stacked line.
+
+### Changed
+- Add type and explicit casting to enable strong mode.
+
+## 0.0.10 - 2015-03-28
+
+### Added
+- First release of charted.
+
+[0.5.0]: https://github.com/google/charted/compare/0.0.10...0.5.0
diff --git a/packages/charted/examples/selection/transitions_demo.dart b/packages/charted/examples/selection/transitions_demo.dart
index f1761af..585fd1e 100644
--- a/packages/charted/examples/selection/transitions_demo.dart
+++ b/packages/charted/examples/selection/transitions_demo.dart
@@ -74,7 +74,7 @@
Transition t2 = new Transition(circle2);
t2
- ..ease = clampEasingFn(identityFunction(easeCubic()))
+ ..ease = clampEasingFn(easeCubic())
..attr('cx', 400)
..duration(4000);
diff --git a/packages/charted/lib/charts/behaviors/chart_tooltip.dart b/packages/charted/lib/charts/behaviors/chart_tooltip.dart
index f64ebe8..551bebc 100644
--- a/packages/charted/lib/charts/behaviors/chart_tooltip.dart
+++ b/packages/charted/lib/charts/behaviors/chart_tooltip.dart
@@ -121,15 +121,17 @@
var tooltipItems = _tooltipRoot.selectAll('.tooltip-item');
tooltipItems.append('div')
..classed('tooltip-item-label')
- ..textWithCallback((int d, i, c) => _area.data.columns
- .elementAt((showSelectedMeasure) ? d : e.series.measures.elementAt(i))
+ ..textWithCallback((d, i, c) => _area.data.columns
+ .elementAt(
+ (showSelectedMeasure) ? d as int : e.series.measures.elementAt(i))
.label);
// Display the value of the currently active series
tooltipItems.append('div')
..classed('tooltip-item-value')
..styleWithCallback('color', (d, i, c) => _area.theme.getColorForKey(d))
- ..textWithCallback((int d, i, c) {
+ ..textWithCallback((_d, i, c) {
+ int d = _d;
var formatter = _getFormatterForColumn(d),
value = _area.data.rows.elementAt(e.row).elementAt(d);
return (formatter != null) ? formatter(value) : value.toString();
diff --git a/packages/charted/lib/charts/behaviors/hovercard.dart b/packages/charted/lib/charts/behaviors/hovercard.dart
index c6a73d6..a12951c 100644
--- a/packages/charted/lib/charts/behaviors/hovercard.dart
+++ b/packages/charted/lib/charts/behaviors/hovercard.dart
@@ -180,8 +180,8 @@
}
}
- void _positionAtMousePointer(ChartEvent e) =>
- _positionAtPoint(e.chartX, e.chartY, _HOVERCARD_OFFSET, _HOVERCARD_OFFSET, false, false);
+ void _positionAtMousePointer(ChartEvent e) => _positionAtPoint(
+ e.chartX, e.chartY, _HOVERCARD_OFFSET, _HOVERCARD_OFFSET, false, false);
void _positionOnLayout(column, row) {
// Currently for layouts, when hovercard is triggered due to change
@@ -209,12 +209,11 @@
dimensionCenterOffset = dimensionOffset;
}
- var rowData = area.data.rows.elementAt(row),
- isNegative = false;
+ var rowData = area.data.rows.elementAt(row), isNegative = false;
num measurePosition = 0,
- dimensionPosition = dimensionScale
- .scale(rowData.elementAt(dimensionCol)) +
- dimensionCenterOffset;
+ dimensionPosition =
+ dimensionScale.scale(rowData.elementAt(dimensionCol)) +
+ dimensionCenterOffset;
if (_isMultiValue) {
num max = SMALL_INT_MIN, min = SMALL_INT_MAX;
@@ -248,14 +247,12 @@
var rect = _hovercardRoot.getBoundingClientRect(),
width = rect.width,
height = rect.height,
- scaleToHostY = (_area.theme.padding != null
- ? _area.theme.padding.top
- : 0) +
- (_area.layout.renderArea.y),
- scaleToHostX = (_area.theme.padding != null
- ? _area.theme.padding.start
- : 0) +
- (_area.layout.renderArea.x),
+ scaleToHostY =
+ (_area.theme.padding != null ? _area.theme.padding.top : 0) +
+ (_area.layout.renderArea.y),
+ scaleToHostX =
+ (_area.theme.padding != null ? _area.theme.padding.start : 0) +
+ (_area.layout.renderArea.x),
renderAreaHeight = _area.layout.renderArea.height,
renderAreaWidth = _area.layout.renderArea.width;
@@ -342,7 +339,7 @@
measureVals.add(_createHovercardItem(column, row));
});
} else if (_columnsToShow.length > 1 || _isMultiValue) {
- var displayedCols = [];
+ var displayedCols = <int>[];
_area.config.series.forEach((ChartSeries series) {
series.measures.forEach((int column) {
if (!displayedCols.contains(column)) displayedCols.add(column);
diff --git a/packages/charted/lib/charts/behaviors/line_marker.dart b/packages/charted/lib/charts/behaviors/line_marker.dart
index f3cffb5..78779d1 100644
--- a/packages/charted/lib/charts/behaviors/line_marker.dart
+++ b/packages/charted/lib/charts/behaviors/line_marker.dart
@@ -58,8 +58,8 @@
assert(index == 0 || index == 1 && _area.useTwoDimensionAxes);
- var dimensionAtBottom =
- index == 1 && _isLeftAxisPrimary || index == 0 && !_isLeftAxisPrimary,
+ var dimensionAtBottom = index == 1 && _isLeftAxisPrimary ||
+ index == 0 && !_isLeftAxisPrimary,
scale = _area.dimensionScales.elementAt(index),
scaled = scale.scale(positions[column]) as num,
theme = _area.theme.getDimensionAxisTheme(),
@@ -95,7 +95,8 @@
if (!_area.isReady) return;
_markers = _parent.selectAll('.line-marker').data(positions.keys);
- _markers.enter.append('path').each((int d, i, e) {
+ _markers.enter.append('path').each((_d, i, e) {
+ int d = _d;
e.classes.add('line-marker');
e.attributes['d'] = _getMarkerPath(d, animate);
});
@@ -103,7 +104,7 @@
if (animate) {
_markers
.transition()
- .attrWithCallback('d', (int d, i, e) => _getMarkerPath(d, false));
+ .attrWithCallback('d', (d, i, e) => _getMarkerPath(d as int, false));
}
_markers.exit.remove();
diff --git a/packages/charted/lib/charts/cartesian_renderers/bar_chart_renderer.dart b/packages/charted/lib/charts/cartesian_renderers/bar_chart_renderer.dart
index 658a605..f411ae2 100644
--- a/packages/charted/lib/charts/cartesian_renderers/bar_chart_renderer.dart
+++ b/packages/charted/lib/charts/cartesian_renderers/bar_chart_renderer.dart
@@ -85,15 +85,16 @@
// Create and update the bars
// Avoids animation on first render unless alwaysAnimate is set to true.
- var bar =
- groups.selectAll('.bar-rdr-bar').dataWithCallback((d, i, c) => rows[i]),
+ var bar = groups
+ .selectAll('.bar-rdr-bar')
+ .dataWithCallback((d, i, c) => rows[i]),
scaled0 = measureScale.scale(0).round();
var getBarLength = (num d) {
var scaledVal = measureScale.scale(d).round();
num ht = verticalBars
- ? (d >= 0 ? scaled0 - scaledVal : scaledVal - scaled0)
- : (d >= 0 ? scaledVal - scaled0 : scaled0 - scaledVal);
+ ? (d >= 0 ? scaled0 - scaledVal : scaledVal - scaled0)
+ : (d >= 0 ? scaledVal - scaled0 : scaled0 - scaledVal);
ht = ht - strokeWidth;
// If bar would be scaled to 0 height but data is not 0, render bar
@@ -131,13 +132,17 @@
RADIUS);
} else {
var fn = d > 0 ? rightRoundedRect : leftRoundedRect;
- return fn(getBarPos(d) as int, (bars.scale(i) as num).toInt() +
- strokeWidthOffset,
- animate ? 0 : getBarLength(d) as int, barWidth as int, RADIUS);
+ return fn(
+ getBarPos(d) as int,
+ (bars.scale(i) as num).toInt() + strokeWidthOffset,
+ animate ? 0 : getBarLength(d) as int,
+ barWidth as int,
+ RADIUS);
}
};
- bar.enter.appendWithCallback((num d, i, e) {
+ bar.enter.appendWithCallback((_d, i, e) {
+ num d = _d;
var rect = Namespace.createChildElement('path', e),
measure = series.measures.elementAt(i),
row = int.parse(e.dataset['row']),
@@ -164,9 +169,10 @@
}
return rect;
})
- ..on('click', (num d, i, e) => _event(mouseClickController, d, i, e))
- ..on('mouseover', (num d, i, e) => _event(mouseOverController, d, i, e))
- ..on('mouseout', (num d, i, e) => _event(mouseOutController, d, i, e));
+ ..on('click', (d, i, e) => _event(mouseClickController, d as num, i, e))
+ ..on(
+ 'mouseover', (d, i, e) => _event(mouseOverController, d as num, i, e))
+ ..on('mouseout', (d, i, e) => _event(mouseOutController, d as num, i, e));
if (animateBarGroups) {
bar.each((d, i, e) {
@@ -190,7 +196,7 @@
});
bar.transition()
- ..attrWithCallback('d', (num d, i, e) => buildPath(d, i, false));
+ ..attrWithCallback('d', (d, i, e) => buildPath(d as num, i, false));
}
bar.exit.remove();
diff --git a/packages/charted/lib/charts/cartesian_renderers/bubble_chart_renderer.dart b/packages/charted/lib/charts/cartesian_renderers/bubble_chart_renderer.dart
index 60ee6e6..76884f6 100644
--- a/packages/charted/lib/charts/cartesian_renderers/bubble_chart_renderer.dart
+++ b/packages/charted/lib/charts/cartesian_renderers/bubble_chart_renderer.dart
@@ -46,7 +46,7 @@
yDimensionScale = area.dimensionScales.last,
theme = area.theme,
bubbleRadiusFactor =
- maxBubbleRadius / min([geometry.width, geometry.height]);
+ maxBubbleRadius / min([geometry.width, geometry.height]);
String color(int i) => theme.getColorForKey(series.measures.elementAt(i));
@@ -108,8 +108,7 @@
Extent get extent {
assert(series != null && area != null);
List<List<num>> rows = area.data.rows;
- num max = rows.first[series.measures.first],
- min = max;
+ num max = rows.first[series.measures.first], min = max;
rows.forEach((row) {
series.measures.forEach((int idx) {
diff --git a/packages/charted/lib/charts/cartesian_renderers/line_chart_renderer.dart b/packages/charted/lib/charts/cartesian_renderers/line_chart_renderer.dart
index c3dc7a3..fc0a12a 100644
--- a/packages/charted/lib/charts/cartesian_renderers/line_chart_renderer.dart
+++ b/packages/charted/lib/charts/cartesian_renderers/line_chart_renderer.dart
@@ -75,8 +75,8 @@
}
var line = new SvgLine(
- xValueAccessor: (d, i) => (dimensionScale.scale(x[i]) as num) +
- rangeBandOffset,
+ xValueAccessor: (d, i) =>
+ (dimensionScale.scale(x[i]) as num) + rangeBandOffset,
yValueAccessor: (d, i) => measureScale.scale(d) as num);
// Add lines and hook up hover and selection events.
@@ -151,7 +151,8 @@
});
linePoints
- ..each((int d, i, e) {
+ ..each((_d, i, e) {
+ int d = _d;
var color = colorForColumn(d);
e.attributes
..['r'] = '4'
@@ -174,7 +175,8 @@
}
var yScale = area.measureScales(series).first;
- root.selectAll('.line-rdr-point').each((int d, i, e) {
+ root.selectAll('.line-rdr-point').each((_d, i, e) {
+ int d = _d;
num x = _xPositions[row],
measureVal = area.data.rows.elementAt(row).elementAt(d);
if (measureVal != null && measureVal.isFinite) {
@@ -262,9 +264,8 @@
if (mouseClickController != null && e.tagName == 'circle') {
var row = int.parse(e.dataset['row']),
column = int.parse(e.dataset['column']);
- mouseClickController.add(
- new DefaultChartEventImpl(scope.event, area, series, row, column,
- d as num));
+ mouseClickController.add(new DefaultChartEventImpl(
+ scope.event, area, series, row, column, d as num));
}
}
@@ -275,9 +276,8 @@
if (mouseOverController != null && e.tagName == 'circle') {
_savedOverRow = int.parse(e.dataset['row']);
_savedOverColumn = int.parse(e.dataset['column']);
- mouseOverController.add(new DefaultChartEventImpl(
- scope.event, area, series, _savedOverRow, _savedOverColumn,
- d as num));
+ mouseOverController.add(new DefaultChartEventImpl(scope.event, area,
+ series, _savedOverRow, _savedOverColumn, d as num));
}
}
@@ -287,9 +287,8 @@
area.state.preview = null;
}
if (mouseOutController != null && e.tagName == 'circle') {
- mouseOutController.add(new DefaultChartEventImpl(
- scope.event, area, series, _savedOverRow, _savedOverColumn,
- d as num));
+ mouseOutController.add(new DefaultChartEventImpl(scope.event, area,
+ series, _savedOverRow, _savedOverColumn, d as num));
}
}
}
diff --git a/packages/charted/lib/charts/cartesian_renderers/stackedbar_chart_renderer.dart b/packages/charted/lib/charts/cartesian_renderers/stackedbar_chart_renderer.dart
index 3109199..a995bec 100644
--- a/packages/charted/lib/charts/cartesian_renderers/stackedbar_chart_renderer.dart
+++ b/packages/charted/lib/charts/cartesian_renderers/stackedbar_chart_renderer.dart
@@ -74,9 +74,9 @@
..duration(theme.transitionDurationMilliseconds);
}
- var bar =
- groups.selectAll('.stack-rdr-bar').dataWithCallback((d, i, c) =>
- d as Iterable);
+ var bar = groups
+ .selectAll('.stack-rdr-bar')
+ .dataWithCallback((d, i, c) => d as Iterable);
var prevOffsetVal = new List();
@@ -175,10 +175,10 @@
? RADIUS
: 0;
var path = (length != 0)
- ? verticalBars
- ? topRoundedRect(0, position, barWidth, length, radius)
- : rightRoundedRect(position, 0, length, barWidth, radius)
- : '';
+ ? verticalBars
+ ? topRoundedRect(0, position, barWidth, length, radius)
+ : rightRoundedRect(position, 0, length, barWidth, radius)
+ : '';
e.attributes['data-offset'] =
verticalBars ? position.toString() : (position + length).toString();
return path;
diff --git a/packages/charted/lib/charts/cartesian_renderers/stackedline_chart_renderer.dart b/packages/charted/lib/charts/cartesian_renderers/stackedline_chart_renderer.dart
index 55306f1..37dc579 100644
--- a/packages/charted/lib/charts/cartesian_renderers/stackedline_chart_renderer.dart
+++ b/packages/charted/lib/charts/cartesian_renderers/stackedline_chart_renderer.dart
@@ -67,8 +67,9 @@
// Second Half: current accumulated values (need for drawing)
var lines = reversedMeasures.map((column) {
var row = area.data.rows.map((values) => values[column]).toList();
- return accumulated.reversed.toList()..addAll(
- new List.generate(x.length, (i) => accumulated[i] += row[i] as num));
+ return accumulated.reversed.toList()
+ ..addAll(new List.generate(
+ x.length, (i) => accumulated[i] += row[i] as num));
}).toList();
var rangeBandOffset =
@@ -77,9 +78,9 @@
// If tracking data points is enabled, cache location of points that
// represent data.
if (trackDataPoints) {
- _xPositions =
- x.map((val) =>
- (dimensionScale.scale(val) + rangeBandOffset) as num).toList();
+ _xPositions = x
+ .map((val) => (dimensionScale.scale(val) + rangeBandOffset) as num)
+ .toList();
}
var fillLine = new SvgLine(
@@ -92,12 +93,13 @@
},
yValueAccessor: (d, i) => measureScale.scale(d) as num);
var strokeLine = new SvgLine(
- xValueAccessor: (d, i) => (dimensionScale.scale(x[i]) as num) +
- rangeBandOffset,
+ xValueAccessor: (d, i) =>
+ (dimensionScale.scale(x[i]) as num) + rangeBandOffset,
yValueAccessor: (d, i) => measureScale.scale(d) as num);
// Add lines and hook up hover and selection events.
- var svgLines = root.selectAll('.stacked-line-rdr-line').data(lines.reversed);
+ var svgLines =
+ root.selectAll('.stacked-line-rdr-line').data(lines.reversed);
svgLines.enter.append('g');
svgLines.each((d, i, e) {
@@ -114,8 +116,8 @@
..['stroke'] = color
..['fill'] = color
..['class'] = styles.isEmpty
- ? 'stacked-line-rdr-line'
- : 'stacked-line-rdr-line ${styles.join(' ')}'
+ ? 'stacked-line-rdr-line'
+ : 'stacked-line-rdr-line ${styles.join(' ')}'
..['data-column'] = '$column';
fill.attributes
..['d'] = fillLine.path(fillData, i, e)
@@ -153,9 +155,7 @@
Extent get extent {
assert(area != null && series != null);
var rows = area.data.rows;
- num max = SMALL_INT_MIN,
- min = SMALL_INT_MAX,
- rowIndex = 0;
+ num max = SMALL_INT_MIN, min = SMALL_INT_MAX;
rows.forEach((row) {
num line = null;
@@ -168,7 +168,6 @@
});
if (line > max) max = line;
if (line < min) min = line;
- rowIndex++;
});
return new Extent(min, max);
@@ -197,7 +196,8 @@
}
void _createTrackingCircles() {
- var linePoints = root.selectAll('.stacked-line-rdr-point')
+ var linePoints = root
+ .selectAll('.stacked-line-rdr-point')
.data(series.measures.toList().reversed);
linePoints.enter.append('circle').each((d, i, e) {
e.classes.add('stacked-line-rdr-point');
@@ -205,7 +205,8 @@
});
linePoints
- ..each((int d, i, e) {
+ ..each((_d, i, e) {
+ int d = _d;
var color = colorForColumn(d);
e.attributes
..['r'] = '4'
@@ -227,12 +228,13 @@
_createTrackingCircles();
}
- double cumulated = 0.0;
+ double cumulated = 0.0;
var yScale = area.measureScales(series).first;
- root.selectAll('.stacked-line-rdr-point').each((int d, i, e) {
+ root.selectAll('.stacked-line-rdr-point').each((_d, i, e) {
+ int d = _d;
var x = _xPositions[row],
measureVal =
- cumulated += area.data.rows.elementAt(row).elementAt(d) as num;
+ cumulated += area.data.rows.elementAt(row).elementAt(d) as num;
if (measureVal != null && measureVal.isFinite) {
var color = colorForColumn(d), filter = filterForColumn(d);
e.attributes
@@ -318,9 +320,8 @@
if (mouseClickController != null && e.tagName == 'circle') {
var row = int.parse(e.dataset['row']),
column = int.parse(e.dataset['column']);
- mouseClickController.add(
- new DefaultChartEventImpl(scope.event, area, series, row, column,
- d as int));
+ mouseClickController.add(new DefaultChartEventImpl(
+ scope.event, area, series, row, column, d as int));
}
}
@@ -331,9 +332,8 @@
if (mouseOverController != null && e.tagName == 'circle') {
_savedOverRow = int.parse(e.dataset['row']);
_savedOverColumn = int.parse(e.dataset['column']);
- mouseOverController.add(new DefaultChartEventImpl(
- scope.event, area, series, _savedOverRow, _savedOverColumn,
- d as int));
+ mouseOverController.add(new DefaultChartEventImpl(scope.event, area,
+ series, _savedOverRow, _savedOverColumn, d as int));
}
}
@@ -343,9 +343,8 @@
area.state.preview = null;
}
if (mouseOutController != null && e.tagName == 'circle') {
- mouseOutController.add(new DefaultChartEventImpl(
- scope.event, area, series, _savedOverRow, _savedOverColumn,
- d as int));
+ mouseOutController.add(new DefaultChartEventImpl(scope.event, area,
+ series, _savedOverRow, _savedOverColumn, d as int));
}
}
}
diff --git a/packages/charted/lib/charts/chart_config.dart b/packages/charted/lib/charts/chart_config.dart
index 0baae21..5a3be52 100644
--- a/packages/charted/lib/charts/chart_config.dart
+++ b/packages/charted/lib/charts/chart_config.dart
@@ -66,8 +66,8 @@
bool switchAxesForRTL;
/// Factory method to create an instance of the default implementation
- factory ChartConfig(Iterable<ChartSeries> series,
- Iterable<int> dimensions) = DefaultChartConfigImpl;
+ factory ChartConfig(Iterable<ChartSeries> series, Iterable<int> dimensions) =
+ DefaultChartConfigImpl;
}
///
diff --git a/packages/charted/lib/charts/chart_data.dart b/packages/charted/lib/charts/chart_data.dart
index 1367cc1..9e6b553 100644
--- a/packages/charted/lib/charts/chart_data.dart
+++ b/packages/charted/lib/charts/chart_data.dart
@@ -20,8 +20,9 @@
Iterable<List> get rows;
/// Create a new instance of [ChartData]'s internal implementation
- factory ChartData(Iterable<ChartColumnSpec> columns,
- Iterable<Iterable> rows) = DefaultChartDataImpl;
+ factory ChartData(
+ Iterable<ChartColumnSpec> columns, Iterable<Iterable> rows) =
+ DefaultChartDataImpl;
}
///
diff --git a/packages/charted/lib/charts/charts.dart b/packages/charted/lib/charts/charts.dart
index ad24994..8fac9b8 100644
--- a/packages/charted/lib/charts/charts.dart
+++ b/packages/charted/lib/charts/charts.dart
@@ -25,7 +25,7 @@
import 'package:charted/svg/shapes.dart';
import 'package:charted/selection/transition.dart';
-import 'package:collection/equality.dart';
+import 'package:collection/collection.dart';
import 'package:logging/logging.dart';
import 'package:observable/observable.dart';
import 'package:quiver/core.dart';
diff --git a/packages/charted/lib/charts/data_transformers/aggregation.dart b/packages/charted/lib/charts/data_transformers/aggregation.dart
index 475a657..9633777 100644
--- a/packages/charted/lib/charts/data_transformers/aggregation.dart
+++ b/packages/charted/lib/charts/data_transformers/aggregation.dart
@@ -11,7 +11,7 @@
///Function callback to filter items in the input
typedef bool AggregationFilterFunc(var item);
-typedef int CompareFunc(dynamic a, dynamic b);
+typedef int CompareFunc(Comparable a, Comparable b);
typedef dynamic FieldAccessor(dynamic item, dynamic key);
@@ -60,7 +60,7 @@
List<int> _sorted;
// Enumeration map for dimension values
- List<Map<dynamic, int>> _dimToIntMap;
+ List<Map<Comparable, int>> _dimToIntMap;
// Sort orders for dimension values
List<List<int>> _dimSortOrders;
@@ -245,8 +245,8 @@
// Create both when object is created and groupBy is called
// Reuse dimension enumerations if possible.
var oldDimToInt = _dimToIntMap;
- _dimToIntMap = new List<Map<dynamic, int>>.generate(_dimFields.length,
- (i) => i < _dimPrefixLength ? oldDimToInt[i] : new Map<dynamic, int>());
+ _dimToIntMap = new List<Map<Comparable, int>>.generate(_dimFields.length,
+ (i) => i < _dimPrefixLength ? oldDimToInt[i] : <Comparable, int>{});
}
/// Check cache entries
@@ -255,7 +255,7 @@
/// if they aren't valid anymore.
/// Update the entities that are valid after the groupBy.
void _updateCachedEntities() {
- List keys = new List.from(_entityCache.keys, growable: false);
+ var keys = new List<String>.from(_entityCache.keys, growable: false);
keys.forEach((String key) {
_AggregationItemImpl entity = _entityCache[key];
if (entity == null) {
@@ -274,7 +274,8 @@
final Map<String, List> _parsedKeys = {};
/// Get value from a map-like object
- dynamic _fetch(var item, String key) {
+ dynamic _fetch(var item, var _key) {
+ var key = _key as String;
if (walkThroughMap && key.contains('.')) {
return walk(item, key, _parsedKeys);
} else {
@@ -317,7 +318,7 @@
// Enumerate the dimension values and cache enumerated rows
for (int di = 0; di < dimensionsCount; di++) {
- var dimensionVal = dimensionAccessor(item, _dimFields[di]);
+ Comparable dimensionVal = dimensionAccessor(item, _dimFields[di]);
int dimensionValEnum = _dimToIntMap[di][dimensionVal];
if (dimensionValEnum == null) {
_dimToIntMap[di][dimensionVal] = dimensionValCount[di];
@@ -336,8 +337,8 @@
return oldSortOrders[i];
}
- List dimensionVals = new List.from(_dimToIntMap[i].keys);
- List<int> retval = new List<int>(_dimToIntMap[i].length);
+ var dimensionVals = new List<Comparable>.from(_dimToIntMap[i].keys);
+ var retval = new List<int>(_dimToIntMap[i].length);
// When a comparator is not specified, our implementation of the
// comparator tries to gracefully handle null values.
@@ -576,7 +577,7 @@
if (di < 0) {
return null;
}
- List values = new List.from(_dimToIntMap[di].keys);
+ var values = new List<Comparable>.from(_dimToIntMap[di].keys);
if (comparators.containsKey(dimensionFieldName)) {
values.sort(comparators[dimensionFieldName]);
} else {
@@ -594,7 +595,8 @@
/// and outputs:
/// ["list", {"key": "val", "val": "m"},
/// "another", {"state": "good"}, "state"]
-List _parseKey(String key, Map parsedKeysCache) {
+List /* <String|Map<String, String>> */ _parseKey(
+ String key, Map parsedKeysCache) {
List parts = parsedKeysCache == null ? null : parsedKeysCache[key];
if (parts == null && key != null) {
parts = new List();
@@ -674,7 +676,7 @@
return parts.fold(initial, (current, part) {
if (current == null) {
return null;
- } else if (current is List && part is Map) {
+ } else if (current is List && part is Map<String, dynamic>) {
for (int i = 0; i < current.length; i++) {
bool match = true;
part.forEach((String key, val) {
diff --git a/packages/charted/lib/charts/data_transformers/aggregation_item.dart b/packages/charted/lib/charts/data_transformers/aggregation_item.dart
index fd739c6..05289ad 100644
--- a/packages/charted/lib/charts/data_transformers/aggregation_item.dart
+++ b/packages/charted/lib/charts/data_transformers/aggregation_item.dart
@@ -145,7 +145,7 @@
}
var lowerDimensionField = model._dimFields[dimensions.length];
- List lowerVals = model.valuesForDimension(lowerDimensionField);
+ List<String> lowerVals = model.valuesForDimension(lowerDimensionField);
lowerVals.forEach((String name) {
List<String> lowerDims = new List.from(dimensions)..add(name);
diff --git a/packages/charted/lib/charts/data_transformers/aggregation_transformer.dart b/packages/charted/lib/charts/data_transformers/aggregation_transformer.dart
index cca0924..6528777 100644
--- a/packages/charted/lib/charts/data_transformers/aggregation_transformer.dart
+++ b/packages/charted/lib/charts/data_transformers/aggregation_transformer.dart
@@ -34,7 +34,8 @@
AggregationModel _model;
bool _expandAllDimension = false;
List<int> _selectedColumns = [];
- FieldAccessor _indexFieldAccessor = (List row, int index) => row[index];
+ FieldAccessor _indexFieldAccessor =
+ (row, index) => (row as List)[index as int];
ChartData _data;
AggregationTransformer(this._dimensionColumnIndices, this._factsColumnIndices,
@@ -92,7 +93,8 @@
// Process rows.
rows.clear();
var transformedRows = <List>[];
- for (String value in _model.valuesForDimension(_dimensionColumnIndices[0])) {
+ for (String value
+ in _model.valuesForDimension(_dimensionColumnIndices[0])) {
_generateAggregatedRow(transformedRows, [value]);
}
rows.addAll(transformedRows);
@@ -179,7 +181,8 @@
/// Expands all dimensions.
void expandAll() {
if (_model != null) {
- for (String value in _model.valuesForDimension(_dimensionColumnIndices[0])) {
+ for (String value
+ in _model.valuesForDimension(_dimensionColumnIndices[0])) {
_expandAll([value]);
}
_expandAllDimension = false;
diff --git a/packages/charted/lib/charts/layout_renderers/pie_chart_renderer.dart b/packages/charted/lib/charts/layout_renderers/pie_chart_renderer.dart
index de00cf7..44727cb 100644
--- a/packages/charted/lib/charts/layout_renderers/pie_chart_renderer.dart
+++ b/packages/charted/lib/charts/layout_renderers/pie_chart_renderer.dart
@@ -98,12 +98,14 @@
indices = indices.reversed.toList();
}
- num accessor(int d, int i) {
+ num accessor(dynamic _d, int i) {
+ var d = _d as int;
var row = d == SMALL_INT_MAX ? otherRow : area.data.rows.elementAt(d);
return row == null || row.elementAt(measure) == null
? 0
: row.elementAt(measure) as num;
}
+
var data = (new PieLayout()..accessor = accessor).layout(indices);
var arc = new SvgArc(
innerRadiusCallback: (d, i, e) => innerRadiusRatio * radius,
@@ -149,9 +151,9 @@
_legend.clear();
var items = new List<ChartLegendItem>.generate(data.length, (i) {
SvgArcData d = data.elementAt(i);
- Iterable row =
- d.data == SMALL_INT_MAX ? otherRow : area.data.rows.elementAt(
- d.data as int);
+ Iterable row = d.data == SMALL_INT_MAX
+ ? otherRow
+ : area.data.rows.elementAt(d.data as int);
return new ChartLegendItem(
index: d.data as int,
diff --git a/packages/charted/lib/charts/src/cartesian_area_impl.dart b/packages/charted/lib/charts/src/cartesian_area_impl.dart
index cc36443..a73ace3 100644
--- a/packages/charted/lib/charts/src/cartesian_area_impl.dart
+++ b/packages/charted/lib/charts/src/cartesian_area_impl.dart
@@ -85,8 +85,8 @@
bool _pendingLegendUpdate = false;
bool _pendingAxisConfigUpdate = false;
- List<ChartBehavior> _behaviors = new List<ChartBehavior>();
- Map<ChartSeries, _ChartSeriesInfo> _seriesInfoCache = new Map();
+ List<ChartBehavior> _behaviors = [];
+ Map<ChartSeries, _ChartSeriesInfo> _seriesInfoCache = {};
StreamController<ChartEvent> _valueMouseOverController;
StreamController<ChartEvent> _valueMouseOutController;
@@ -214,8 +214,8 @@
/// if one was not already created for the given dimension [column].
DefaultChartAxisImpl _getDimensionAxis(int column) {
_dimensionAxes.putIfAbsent(column, () {
- var axisConf = config.getDimensionAxis(column),
- axis = axisConf != null
+ var axisConf = config.getDimensionAxis(column);
+ var axis = axisConf != null
? new DefaultChartAxisImpl.withAxisConfig(this, axisConf)
: new DefaultChartAxisImpl(this);
return axis;
@@ -316,7 +316,8 @@
String transform =
'translate(${layout.renderArea.x},${layout.renderArea.y})';
- selection.each((ChartSeries s, _, Element group) {
+ selection.each((_s, _, Element group) {
+ ChartSeries s = _s;
_ChartSeriesInfo info = _seriesInfoCache[s];
if (info == null) {
info = _seriesInfoCache[s] = new _ChartSeriesInfo(this, s);
@@ -329,19 +330,16 @@
// A series that was rendered earlier isn't there anymore, remove it
selection.exit
- ..each((ChartSeries s, _, __) {
+ ..each((_s, _, __) {
+ ChartSeries s = _s;
var info = _seriesInfoCache.remove(s);
- if (info != null) {
- info.dispose();
- }
+ info?.dispose();
})
..remove();
// Notify on the stream that the chart has been updated.
isReady = true;
- if (_chartAxesUpdatedController != null) {
- _chartAxesUpdatedController.add(this);
- }
+ _chartAxesUpdatedController?.add(this);
});
// Save the list of valid series and initialize axes.
@@ -360,7 +358,7 @@
/// Initialize the axes - required even if the axes are not being displayed.
_initAxes({bool preRender: false}) {
- Map measureAxisUsers = <String, Iterable<ChartSeries>>{};
+ var measureAxisUsers = <String, List<ChartSeries>>{};
var keysToRemove = _measureAxes.keys.toList();
// Create necessary measures axes.
@@ -392,7 +390,7 @@
var sampleCol = listOfSeries.first.measures.first,
sampleColSpec = data.columns.elementAt(sampleCol),
axis = _getMeasureAxis(id);
- List domain;
+ List<num> domain;
if (sampleColSpec.useOrdinalScale) {
throw new UnsupportedError(
@@ -400,10 +398,11 @@
} else {
// Extent is available because [ChartRenderer.prepare] was already
// called (when checking for valid series in [draw].
- Iterable extents = listOfSeries.map((s) =>
- (s.renderer as CartesianRenderer).extent).toList();
- var lowest = min(extents.map((e) => e.min as num)),
- highest = max(extents.map((e) => e.max as num));
+ var extents = listOfSeries
+ .map((s) => (s.renderer as CartesianRenderer).extent)
+ .toList();
+ var lowest = min(extents.map((e) => e.min as num));
+ var highest = max(extents.map((e) => e.max as num));
// Use default domain if lowest and highest are the same, right now
// lowest is always 0 unless it is less than 0 - change to lowest when
@@ -420,18 +419,20 @@
// Configure dimension axes.
int dimensionAxesCount = useTwoDimensionAxes ? 2 : 1;
config.dimensions.take(dimensionAxesCount).forEach((int column) {
- var axis = _getDimensionAxis(column),
- sampleColumnSpec = data.columns.elementAt(column),
- values = data.rows.map((row) => row.elementAt(column));
- List domain;
+ var axis = _getDimensionAxis(column);
+ var sampleColumnSpec = data.columns.elementAt(column);
+
+ Iterable values = data.rows
+ .map((row) => row.elementAt(column));
if (sampleColumnSpec.useOrdinalScale) {
- domain = values.map((e) => e.toString()).toList();
+ List<String> domain = values.map((v) => v.toString()).toList();
+ axis.initAxisDomain(column, true, domain);
} else {
- var extent = new Extent.items(values);
- domain = [extent.min, extent.max];
+ var extent = new Extent.items(values.cast<num>());
+ List<num> domain = [extent.min, extent.max];
+ axis.initAxisDomain(column, true, domain);
}
- axis.initAxisDomain(column, true, domain);
});
// See if any dimensions need "band" on the axis.
@@ -448,13 +449,13 @@
// List of measure and dimension axes that are displayed
assert(isNullOrEmpty(config.displayedMeasureAxes) ||
config.displayedMeasureAxes.length < 2);
- var measureAxesCount = dimensionAxesCount == 1 ? 2 : 0,
- displayedMeasureAxes = (isNullOrEmpty(config.displayedMeasureAxes)
+ var measureAxesCount = dimensionAxesCount == 1 ? 2 : 0;
+ var displayedMeasureAxes = (isNullOrEmpty(config.displayedMeasureAxes)
? _measureAxes.keys.take(measureAxesCount)
: config.displayedMeasureAxes.take(measureAxesCount))
- .toList(growable: false),
- displayedDimensionAxes =
- config.dimensions.take(dimensionAxesCount).toList(growable: false);
+ .toList(growable: false);
+ var displayedDimensionAxes =
+ config.dimensions.take(dimensionAxesCount).toList(growable: false);
// Compute size of the dimension axes
if (config.renderDimensionAxes != false) {
@@ -475,8 +476,8 @@
? MEASURE_AXIS_ORIENTATIONS.last
: MEASURE_AXIS_ORIENTATIONS.first;
displayedMeasureAxes.asMap().forEach((int index, String key) {
- var axis = _measureAxes[key],
- orientation = _orientRTL(measureAxisOrientations[index]);
+ var axis = _measureAxes[key];
+ var orientation = _orientRTL(measureAxisOrientations[index]);
axis.prepareToDraw(orientation);
layout._axes[orientation] = axis.size;
});
@@ -503,7 +504,8 @@
.data(displayedMeasureAxes);
// Update measure axis (add/remove/update)
axisGroups.enter.append('svg:g');
- axisGroups.each((String axisId, index, group) {
+ axisGroups.each((_axisId, index, group) {
+ String axisId = _axisId;
_getMeasureAxis(axisId).draw(group, _scope, preRender: preRender);
group.attributes['class'] = 'measure-axis-group measure-${index}';
});
@@ -517,7 +519,8 @@
.data(displayedDimensionAxes);
// Update dimension axes (add/remove/update)
dimAxisGroups.enter.append('svg:g');
- dimAxisGroups.each((int column, index, group) {
+ dimAxisGroups.each((_column, index, group) {
+ int column = _column;
_getDimensionAxis(column).draw(group, _scope, preRender: preRender);
group.attributes['class'] = 'dimension-axis-group dim-${index}';
});
@@ -535,7 +538,6 @@
? [layout.renderArea.height, 0]
: [0, layout.renderArea.width]);
}
- ;
}
}
@@ -598,7 +600,7 @@
// Updates the AxisConfig, if configuration chagned since the last time the
// AxisConfig was updated.
- _updateAxisConfig() {
+ void _updateAxisConfig() {
if (!_pendingAxisConfigUpdate) return;
_series.forEach((ChartSeries s) {
var measureAxisIds =
diff --git a/packages/charted/lib/charts/src/chart_axis_impl.dart b/packages/charted/lib/charts/src/chart_axis_impl.dart
index 890a18c..0221f22 100644
--- a/packages/charted/lib/charts/src/chart_axis_impl.dart
+++ b/packages/charted/lib/charts/src/chart_axis_impl.dart
@@ -11,7 +11,7 @@
class DefaultChartAxisImpl {
static const int _AXIS_TITLE_HEIGHT = 20;
- CartesianArea _area;
+ final CartesianArea _area;
ChartAxisConfig config;
ChartAxisTheme _theme;
SvgAxisTicks _axisTicksPlacement;
@@ -37,9 +37,7 @@
_isDimension = isDimension;
// If we don't have a scale yet, create one.
- if (scale == null) {
- _scale = _columnSpec.createDefaultScale();
- }
+ _scale ??= _columnSpec.createDefaultScale();
// We have the scale, get theme.
_theme = isDimension
@@ -56,24 +54,26 @@
_title = config?.title;
}
- void initAxisScale(Iterable range) {
+ void initAxisScale(Iterable<num> range) {
assert(scale != null);
if (scale is OrdinalScale) {
+ Iterable<num> numericRange = range;
var usingBands = _area.dimensionsUsingBands.contains(_column),
innerPadding = usingBands ? _theme.axisBandInnerPadding : 1.0,
- outerPadding =
- usingBands ? _theme.axisBandOuterPadding : _theme.axisOuterPadding;
+ outerPadding = usingBands
+ ? _theme.axisBandOuterPadding
+ : _theme.axisOuterPadding;
// This is because when left axis is primary the first data row should
// appear on top of the y-axis instead of on bottom.
if (_area.config.isLeftAxisPrimary) {
- range = range.toList().reversed;
+ numericRange = numericRange.toList().reversed;
}
if (usingBands) {
(scale as OrdinalScale)
- .rangeRoundBands(range, innerPadding, outerPadding);
+ .rangeRoundBands(numericRange, innerPadding, outerPadding);
} else {
- (scale as OrdinalScale).rangePoints(range, outerPadding);
+ (scale as OrdinalScale).rangePoints(numericRange, outerPadding);
}
} else {
if (_title != null) {
@@ -88,7 +88,8 @@
}
void prepareToDraw(String orientation) {
- if (orientation == null) orientation = ORIENTATION_BOTTOM;
+ assert(_theme != null);
+ orientation ??= ORIENTATION_BOTTOM;
_orientation = orientation;
_isVertical =
_orientation == ORIENTATION_LEFT || _orientation == ORIENTATION_RIGHT;
@@ -104,15 +105,14 @@
// Handle auto re-sizing of horizontal axis.
var ticks = (config != null && !isNullOrEmpty(config.tickValues))
- ? config.tickValues
- : scale.ticks,
-
- formatter = _columnSpec.formatter == null
- ? scale.createTickFormatter()
- : _columnSpec.formatter,
- textMetrics = new TextMetrics(fontStyle: _theme.ticksFont),
- formattedTicks = ticks.map((x) => formatter(x)).toList(),
- shortenedTicks = formattedTicks;
+ ? config.tickValues
+ : scale.ticks,
+ formatter = _columnSpec.formatter == null
+ ? scale.createTickFormatter()
+ : _columnSpec.formatter,
+ textMetrics = new TextMetrics(fontStyle: _theme.ticksFont),
+ formattedTicks = ticks.map((x) => formatter(x)).toList(),
+ shortenedTicks = formattedTicks;
if (_isVertical) {
var width = textMetrics.getLongestTextWidth(formattedTicks).ceil();
if (width > _theme.verticalAxisWidth) {
@@ -199,7 +199,6 @@
set scale(Scale value) {
_scale = value;
}
-
}
class PrecomputedAxisTicks implements SvgAxisTicks {
@@ -235,8 +234,8 @@
formattedTicks = ticks.map((x) => axis.tickFormat(x)).toList();
shortenedTicks = formattedTicks;
- var range = axis.scale.rangeExtent,
- textMetrics = new TextMetrics(fontStyle: ticksFont);
+ Extent<num> range = axis.scale.rangeExtent;
+ var textMetrics = new TextMetrics(fontStyle: ticksFont);
num allowedWidth = (range.max - range.min) ~/ ticks.length,
maxLabelWidth = textMetrics.getLongestTextWidth(formattedTicks);
diff --git a/packages/charted/lib/charts/src/chart_config_impl.dart b/packages/charted/lib/charts/src/chart_config_impl.dart
index a4f2cd7..42d4b06 100644
--- a/packages/charted/lib/charts/src/chart_config_impl.dart
+++ b/packages/charted/lib/charts/src/chart_config_impl.dart
@@ -24,7 +24,6 @@
@override
bool isLeftAxisPrimary = false;
- @override
bool autoResizeAxis = true;
@override
diff --git a/packages/charted/lib/charts/src/chart_data_impl.dart b/packages/charted/lib/charts/src/chart_data_impl.dart
index 79e3a1a..0ba0d11 100644
--- a/packages/charted/lib/charts/src/chart_data_impl.dart
+++ b/packages/charted/lib/charts/src/chart_data_impl.dart
@@ -80,7 +80,8 @@
'Changes on this row will not be monitored');
} else {
_disposer.add(
- (row as ObservableList).listChanges
+ (row as ObservableList)
+ .listChanges
.listen((changes) => _valuesChanged(index, changes)),
row);
}
diff --git a/packages/charted/lib/charts/src/chart_events_impl.dart b/packages/charted/lib/charts/src/chart_events_impl.dart
index 859f21d..b2793e8 100644
--- a/packages/charted/lib/charts/src/chart_events_impl.dart
+++ b/packages/charted/lib/charts/src/chart_events_impl.dart
@@ -35,8 +35,9 @@
DefaultChartEventImpl(this.source, this.area,
[this.series, this.row, this.column, this.value]) {
var hostRect = area.host.getBoundingClientRect(),
- left =
- area.config.isRTL ? area.theme.padding.end : area.theme.padding.start;
+ left = area.config.isRTL
+ ? area.theme.padding.end
+ : area.theme.padding.start;
if (source is MouseEvent) {
MouseEvent mouseSource = source;
chartX = mouseSource.client.x - hostRect.left - left;
diff --git a/packages/charted/lib/charts/src/chart_legend_impl.dart b/packages/charted/lib/charts/src/chart_legend_impl.dart
index d2be775..0af8f35 100644
--- a/packages/charted/lib/charts/src/chart_legend_impl.dart
+++ b/packages/charted/lib/charts/src/chart_legend_impl.dart
@@ -110,11 +110,13 @@
/// Creates legend items starting at the given index.
void _createLegendItems() {
var state = _area.state,
- rows =
- _root.selectAll('.chart-legend-row').data(_items, (x) => x.hashCode),
+ rows = _root
+ .selectAll('.chart-legend-row')
+ .data(_items, (x) => x.hashCode),
isFirstRender = rows.length == 0;
- var enter = rows.enter.appendWithCallback((ChartLegendItem d, i, e) {
+ var enter = rows.enter.appendWithCallback((_d, i, e) {
+ ChartLegendItem d = _d;
var row = Namespace.createChildElement('div', e),
color = Namespace.createChildElement('div', e)
..className = 'chart-legend-color',
@@ -158,7 +160,8 @@
// We have elements in the DOM that need updating.
if (!isFirstRender) {
- rows.each((ChartLegendItem d, i, Element e) {
+ rows.each((_d, i, Element e) {
+ ChartLegendItem d = _d;
var classes = e.classes;
if (state != null) {
if (d.index == state.preview) {
@@ -186,13 +189,15 @@
if (state != null) {
enter
- ..on('mouseover', (ChartLegendItem d, i, e) => state.preview = d.index)
+ ..on('mouseover',
+ (d, i, e) => state.preview = (d as ChartLegendItem).index)
..on('mouseout', (d, i, e) {
if (state.preview == d.index) {
state.preview = null;
}
})
- ..on('click', (ChartLegendItem d, i, e) {
+ ..on('click', (_d, i, e) {
+ ChartLegendItem d = _d;
if (state.isSelected(d.index)) {
state.unselect(d.index);
} else {
diff --git a/packages/charted/lib/charts/src/chart_state_impl.dart b/packages/charted/lib/charts/src/chart_state_impl.dart
index 6b86628..0ddacba 100644
--- a/packages/charted/lib/charts/src/chart_state_impl.dart
+++ b/packages/charted/lib/charts/src/chart_state_impl.dart
@@ -26,7 +26,8 @@
LinkedHashSet<int> hidden = new LinkedHashSet<int>();
LinkedHashSet<int> selection = new LinkedHashSet<int>();
- LinkedHashSet<Pair<int, int>> highlights = new LinkedHashSet<Pair<int, int>>();
+ LinkedHashSet<Pair<int, int>> highlights =
+ new LinkedHashSet<Pair<int, int>>();
Pair<int, int> _hovered;
int _preview;
diff --git a/packages/charted/lib/charts/themes/quantum_theme.dart b/packages/charted/lib/charts/themes/quantum_theme.dart
index de2c034..9185978 100644
--- a/packages/charted/lib/charts/themes/quantum_theme.dart
+++ b/packages/charted/lib/charts/themes/quantum_theme.dart
@@ -39,10 +39,8 @@
static final _MEASURE_AXIS_THEME =
new QuantumChartAxisTheme(ChartAxisTheme.FILL_RENDER_AREA, 5);
- static final _ORDINAL_DIMENSION_AXIS_THEME =
- new QuantumChartAxisTheme(0, 10);
- static final _DEFAULT_DIMENSION_AXIS_THEME =
- new QuantumChartAxisTheme(4, 10);
+ static final _ORDINAL_DIMENSION_AXIS_THEME = new QuantumChartAxisTheme(0, 10);
+ static final _DEFAULT_DIMENSION_AXIS_THEME = new QuantumChartAxisTheme(4, 10);
final OrdinalScale _scale = new OrdinalScale()..range = COLORS;
@@ -71,9 +69,11 @@
@override
String getFilterForState(int state) => state & ChartState.COL_PREVIEW != 0 ||
- state & ChartState.VAL_HOVERED != 0 ||
- state & ChartState.COL_SELECTED != 0 ||
- state & ChartState.VAL_HIGHLIGHTED != 0 ? 'url(#drop-shadow)' : '';
+ state & ChartState.VAL_HOVERED != 0 ||
+ state & ChartState.COL_SELECTED != 0 ||
+ state & ChartState.VAL_HIGHLIGHTED != 0
+ ? 'url(#drop-shadow)'
+ : '';
@override
String getOtherColor([int state = 0]) => OTHER_COLORS is Iterable
diff --git a/packages/charted/lib/core/interpolators/interpolators.dart b/packages/charted/lib/core/interpolators/interpolators.dart
index d13eed1..89b8c29 100644
--- a/packages/charted/lib/core/interpolators/interpolators.dart
+++ b/packages/charted/lib/core/interpolators/interpolators.dart
@@ -10,11 +10,11 @@
/// [Interpolator] accepts [t], such that 0.0 < t < 1.0 and returns
/// a value in a pre-defined range.
-typedef Interpolator(num t);
+typedef Interpolator<S> = S Function(num t);
/// [InterpolatorGenerator] accepts two parameters [a], [b] and returns an
/// [Interpolator] for transitioning from [a] to [b]
-typedef Interpolator InterpolatorGenerator(a, b);
+typedef InterpolatorGenerator<T, S> = Interpolator<S> Function(T a, T b);
/// List of registered interpolators - [createInterpolatorFromRegistry]
/// iterates through this list from backwards and the first non-null
@@ -59,13 +59,13 @@
//
/// Generate a numeric interpolator between numbers [a] and [b]
-Interpolator createNumberInterpolator(num a, num b) {
+Interpolator<num> createNumberInterpolator(num a, num b) {
b -= a;
return (t) => a + b * t;
}
/// Generate a rounded number interpolator between numbers [a] and [b]
-Interpolator createRoundedNumberInterpolator(num a, num b) {
+Interpolator<num> createRoundedNumberInterpolator(num a, num b) {
b -= a;
return (t) => (a + b * t).round();
}
@@ -78,8 +78,8 @@
/// merging the non numeric part of the strings.
///
/// Eg: Interpolate between $100.0 and $150.0
-Interpolator createStringInterpolator(String a, String b) {
- if (a == null || b == null) return (t) => b;
+Interpolator<String> createStringInterpolator(String a, String b) {
+ if (a == null || b == null) return (num t) => b;
// See if both A and B represent colors as RGB or HEX strings.
// If yes, use color interpolators
@@ -95,14 +95,14 @@
new Color.fromHslString(a), new Color.fromHslString(b));
}
- var numberRegEx = new RegExp(r'[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?'),
- numMatchesInA = numberRegEx.allMatches(a),
- numMatchesInB = numberRegEx.allMatches(b),
- stringParts = [],
- numberPartsInA = [],
- numberPartsInB = [],
- interpolators = [],
- s0 = 0;
+ var numberRegEx = new RegExp(r'[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?');
+ var numMatchesInA = numberRegEx.allMatches(a);
+ var numMatchesInB = numberRegEx.allMatches(b);
+ List<String> stringParts = [];
+ List<String> numberPartsInA = [];
+ List<String> numberPartsInB = [];
+ List<Interpolator<num>> interpolators = [];
+ int s0 = 0;
numberPartsInA.addAll(numMatchesInA.map((m) => m.group(0)));
@@ -118,18 +118,16 @@
int maxLength = math.max(numberPartsInA.length, numberPartsInB.length);
for (var i = 0; i < numberLength; i++) {
interpolators.add(createNumberInterpolator(
- num.parse(numberPartsInA[i] as String),
- num.parse(numberPartsInB[i] as String)));
+ num.parse(numberPartsInA[i]), num.parse(numberPartsInB[i])));
}
if (numberPartsInA.length < numberPartsInB.length) {
for (var i = numberLength; i < maxLength; i++) {
interpolators.add(createNumberInterpolator(
- num.parse(numberPartsInB[i] as String),
- num.parse(numberPartsInB[i] as String)));
+ num.parse(numberPartsInB[i]), num.parse(numberPartsInB[i])));
}
}
- return (t) {
+ return (num t) {
StringBuffer sb = new StringBuffer();
for (var i = 0; i < stringParts.length; i++) {
sb.write(stringParts[i]);
@@ -142,32 +140,34 @@
}
/// Generate an interpolator for RGB values.
-Interpolator createRgbColorInterpolator(Color a, Color b) {
- if (a == null || b == null) return (t) => b;
+Interpolator<String> createRgbColorInterpolator(Color a, Color b) {
+ if (a == null || b == null) return (num t) => b.toRgbaString();
var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab;
- return (t) => new Color.fromRgba((ar + br * t).round(), (ag + bg * t).round(),
- (ab + bb * t).round(), 1.0).toRgbaString();
+ return (num t) => new Color.fromRgba((ar + br * t).round(),
+ (ag + bg * t).round(), (ab + bb * t).round(), 1.0)
+ .toRgbaString();
}
/// Generate an interpolator using HSL color system converted to Hex string.
-Interpolator createHslColorInterpolator(Color a, Color b) {
- if (a == null || b == null) return (t) => b;
+Interpolator<String> createHslColorInterpolator(Color a, Color b) {
+ if (a == null || b == null) return (num t) => b.toHslaString();
var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al;
- return (t) => new Color.fromHsla((ah + bh * t).round(), (as + bs * t).round(),
- (al + bl * t).round(), 1.0).toHslaString();
+ return (num t) => new Color.fromHsla((ah + bh * t).round(),
+ (as + bs * t).round(), (al + bl * t).round(), 1.0)
+ .toHslaString();
}
/// Generates an interpolator to interpolate each element between lists
/// [a] and [b] using registered interpolators.
-Interpolator createListInterpolator(List a, List b) {
+Interpolator<List> createListInterpolator(List a, List b) {
if (a == null || b == null) return (t) => b;
- var x = [],
+ var x = <Interpolator>[],
aLength = a.length,
numInterpolated = b.length,
- output = new List<dynamic>.filled(
- math.max(aLength, numInterpolated), null);
+ output =
+ new List<dynamic>.filled(math.max(aLength, numInterpolated), null);
num n0 = math.min(aLength, numInterpolated);
int i;
@@ -175,7 +175,7 @@
for (; i < aLength; ++i) output[i] = a[i];
for (; i < numInterpolated; ++i) output[i] = b[i];
- return (t) {
+ return (num t) {
for (i = 0; i < n0; ++i) output[i] = x[i](t);
return output;
};
@@ -183,7 +183,7 @@
/// Generates an interpolator to interpolate each value on [a] to [b] using
/// registered interpolators.
-Interpolator createMapInterpolator(Map a, Map b) {
+Interpolator<Map> createMapInterpolator(Map a, Map b) {
if (a == null || b == null) return (t) => b;
var interpolatorsMap = new Map(),
output = new Map(),
@@ -212,14 +212,14 @@
/// Returns the interpolator that interpolators two transform strings
/// [a] and [b] by their translate, rotate, scale and skewX parts.
-Interpolator createTransformInterpolator(String a, String b) {
+Interpolator<String> createTransformInterpolator(String a, String b) {
if (a == null || b == null) return (t) => b;
var numRegExStr = r'[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?',
numberRegEx = new RegExp(numRegExStr),
translateRegEx =
- new RegExp(r'translate\(' + '$numRegExStr,$numRegExStr' + r'\)'),
+ new RegExp(r'translate\(' + '$numRegExStr,$numRegExStr' + r'\)'),
scaleRegEx =
- new RegExp(r'scale\(' + numRegExStr + r',' + numRegExStr + r'\)'),
+ new RegExp(r'scale\(' + numRegExStr + r',' + numRegExStr + r'\)'),
rotateRegEx = new RegExp(r'rotate\(' + numRegExStr + r'(deg)?\)'),
skewRegEx = new RegExp(r'skewX\(' + numRegExStr + r'(deg)?\)'),
translateA = translateRegEx.firstMatch(a),
@@ -229,8 +229,7 @@
translateB = translateRegEx.firstMatch(b),
scaleB = scaleRegEx.firstMatch(b),
rotateB = rotateRegEx.firstMatch(b),
- skewB = skewRegEx.firstMatch(b),
- match;
+ skewB = skewRegEx.firstMatch(b);
List<num> numSetA = [], numSetB = [];
String tempStr;
@@ -238,7 +237,7 @@
// translate
if (translateA != null) {
tempStr = a.substring(translateA.start, translateA.end);
- match = numberRegEx.allMatches(tempStr);
+ var match = numberRegEx.allMatches(tempStr);
for (Match m in match) {
numSetA.add(num.parse(m.group(0)));
}
@@ -248,7 +247,7 @@
if (translateB != null) {
tempStr = b.substring(translateB.start, translateB.end);
- match = numberRegEx.allMatches(tempStr);
+ var match = numberRegEx.allMatches(tempStr);
for (Match m in match) {
numSetB.add(num.parse(m.group(0)));
}
@@ -259,7 +258,7 @@
// scale
if (scaleA != null) {
tempStr = a.substring(scaleA.start, scaleA.end);
- match = numberRegEx.allMatches(tempStr);
+ var match = numberRegEx.allMatches(tempStr);
for (Match m in match) {
numSetA.add(num.parse(m.group(0)));
}
@@ -269,7 +268,7 @@
if (scaleB != null) {
tempStr = b.substring(scaleB.start, scaleB.end);
- match = numberRegEx.allMatches(tempStr);
+ var match = numberRegEx.allMatches(tempStr);
for (Match m in match) {
numSetB.add(num.parse(m.group(0)));
}
@@ -280,16 +279,16 @@
// rotate
if (rotateA != null) {
tempStr = a.substring(rotateA.start, rotateA.end);
- match = numberRegEx.firstMatch(tempStr);
- numSetA.add(num.parse((match as Match).group(0)));
+ var match = numberRegEx.firstMatch(tempStr);
+ numSetA.add(num.parse(match.group(0)));
} else {
numSetA.add(0);
}
if (rotateB != null) {
tempStr = b.substring(rotateB.start, rotateB.end);
- match = numberRegEx.firstMatch(tempStr);
- numSetB.add(num.parse((match as Match).group(0)));
+ var match = numberRegEx.firstMatch(tempStr);
+ numSetB.add(num.parse(match.group(0)));
} else {
numSetB.add(0);
}
@@ -306,21 +305,21 @@
// skew
if (skewA != null) {
tempStr = a.substring(skewA.start, skewA.end);
- match = numberRegEx.firstMatch(tempStr);
- numSetA.add(num.parse((match as Match).group(0)));
+ var match = numberRegEx.firstMatch(tempStr);
+ numSetA.add(num.parse(match.group(0)));
} else {
numSetA.add(0);
}
if (skewB != null) {
tempStr = b.substring(skewB.start, skewB.end);
- match = numberRegEx.firstMatch(tempStr);
- numSetB.add(num.parse((match as Match).group(0)));
+ var match = numberRegEx.firstMatch(tempStr);
+ numSetB.add(num.parse(match.group(0)));
} else {
numSetB.add(0);
}
- return (t) {
+ return (num t) {
return 'translate(${createNumberInterpolator(numSetA[0], numSetB[0])(t)},'
'${createNumberInterpolator(numSetA[1], numSetB[1])(t)})'
'scale(${createNumberInterpolator(numSetA[2], numSetB[2])(t)},'
@@ -332,7 +331,7 @@
/// Returns the interpolator that interpolators zoom list [a] to [b]. Zoom
/// lists are described by triple elements [ux0, uy0, w0] and [ux1, uy1, w1].
-Interpolator createZoomInterpolator(List a, List b) {
+Interpolator<List<num>> createZoomInterpolator(List a, List b) {
if (a == null || b == null) return (t) => b;
assert(a.length == b.length && a.length == 3);
@@ -351,7 +350,7 @@
dr = r1 - r0,
S = ((!dr.isNaN) ? dr : math.log(w1 / w0)) / sqrt2;
- return (t) {
+ return (num t) {
var s = t * S;
if (!dr.isNaN) {
// General case.
@@ -360,18 +359,18 @@
return [ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / cosh(sqrt2 * s + r0)];
}
// Special case for u0 ~= u1.
- return [ux0 + t * dx, uy0 + t * dy, w0 * math.exp(sqrt2 * s)];
+ return <num>[ux0 + t * dx, uy0 + t * dy, w0 * math.exp(sqrt2 * s)];
};
}
/// Reverse interpolator for a number.
-Interpolator uninterpolateNumber(num a, num b) {
+Interpolator<num> uninterpolateNumber(num a, num b) {
b = 1 / (b - a);
return (x) => (x - a) * b;
}
/// Reverse interpolator for a clamped number.
-Interpolator uninterpolateClamp(num a, num b) {
+Interpolator<num> uninterpolateClamp(num a, num b) {
b = 1 / (b - a);
return (x) => math.max(0, math.min(1, (x - a) * b));
}
diff --git a/packages/charted/lib/core/scales.dart b/packages/charted/lib/core/scales.dart
index 689d42d..7125ed9 100644
--- a/packages/charted/lib/core/scales.dart
+++ b/packages/charted/lib/core/scales.dart
@@ -28,25 +28,28 @@
part 'scales/log_scale.dart';
part 'scales/time_scale.dart';
-typedef num RoundFunction(num value);
+typedef RoundFunction = num Function(num);
/// Minimum common interface supported by all scales. [QuantitativeScale] and
/// [OrdinalScale] contain the interface for their respective types.
-abstract class Scale {
+abstract class Scale<TDomain extends Comparable, TRange> {
+ static final NumberFormat numberFormatter =
+ new NumberFormat(new EnUsLocale());
+
/// Given a [value] in the input domain, map it to the range.
/// On [QuantitativeScale]s both parameter and return values are numbers.
- dynamic scale(value);
+ TRange scale(TDomain value);
/// Given a [value] in the output range, return value in the input domain
/// that maps to the value.
/// On [QuantitativeScale]s both parameter and return values are numbers.
- dynamic invert(value);
+ TDomain invert(TRange value);
/// Input domain used by this scale.
- Iterable domain;
+ Iterable<TDomain> domain;
/// Output range used by this scale.
- Iterable range;
+ Iterable<TRange> range;
/// Maximum and minimum values of the scale's output range.
Extent get rangeExtent;
@@ -89,8 +92,9 @@
/// Minimum common interface supported by scales whose input domain
/// contains discreet values (Ordinal scales).
-abstract class OrdinalScale extends Scale {
- factory OrdinalScale() = _OrdinalScale;
+abstract class OrdinalScale<TDomain extends Comparable, TRange>
+ extends Scale<TDomain, TRange> {
+ factory OrdinalScale() = _OrdinalScale<TDomain, TRange>;
/// Amount of space that each value in the domain gets from the range. A band
/// is available only after [rangeBands] or [rangeRoundBands] is called by
@@ -100,16 +104,17 @@
/// Maps each value on the domain to a single point on output range. When a
/// non-zero value is specified, [padding] space is left unused on both ends
/// of the range.
- void rangePoints(Iterable range, [double padding]);
+ void rangePoints(Iterable<num> range, [double padding]);
/// Maps each value on the domain to a band in the output range. When a
/// non-zero value is specified, [padding] space is left between each bands
/// and [outerPadding] space is left unused at both ends of the range.
- void rangeBands(Iterable range, [double padding, double outerPadding]);
+ void rangeBands(Iterable<num> range, [double padding, double outerPadding]);
/// Similar to [rangeBands] but ensures that each band starts and ends on a
/// pixel boundary - helps avoid anti-aliasing artifacts.
- void rangeRoundBands(Iterable range, [double padding, double outerPadding]);
+ void rangeRoundBands(Iterable<num> range,
+ [double padding, double outerPadding]);
}
class RoundingFunctions extends Pair<RoundFunction, RoundFunction> {
@@ -119,9 +124,8 @@
factory RoundingFunctions.defaults() =>
new RoundingFunctions((x) => x.floor(), (x) => x.ceil());
- factory RoundingFunctions.identity() => new RoundingFunctions(
- (num n) => identityFunction/*<num>*/(n),
- (num n) => identityFunction/*<num>*/(n));
+ factory RoundingFunctions.identity() =>
+ new RoundingFunctions((num n) => n, (num n) => n);
RoundFunction get floor => super.first;
RoundFunction get ceil => super.last;
@@ -130,15 +134,15 @@
/// Namespacing container for utilities used by scales.
abstract class ScaleUtils {
/// Utility to return extent of sorted [values].
- static Extent extent(Iterable<num> values) => values.first < values.last
- ? new Extent(values.first, values.last)
- : new Extent(values.last, values.first);
+ static Extent<num> extent(Iterable<num> values) => values.first < values.last
+ ? new Extent<num>(values.first, values.last)
+ : new Extent<num>(values.last, values.first);
/// Extends [values] to round numbers based on the given pair of
/// floor and ceil functions. [functions] is a pair of rounding function
/// among which the first is used to compute floor of a number and the
/// second for ceil of the number.
- static List nice(List<num> values, RoundingFunctions functions) {
+ static List<num> nice(List<num> values, RoundingFunctions functions) {
if (values.last >= values.first) {
values[0] = functions.floor(values.first);
values[values.length - 1] = functions.ceil(values.last);
@@ -162,11 +166,14 @@
/// @param range The range of the scale.
/// @param uninterpolator The uninterpolator for domain values.
/// @param interpolator The interpolator for range values.
- static Function bilinearScale(
- List domain, List range, Function uninterpolator, Function interpolator) {
- var u = uninterpolator(domain[0], domain[1]),
- i = interpolator(range[0], range[1]);
- return (x) => i(u(x));
+ static num Function(num) bilinearScale(
+ List<num> domain,
+ List<num> range,
+ InterpolatorGenerator<num, num> uninterpolator,
+ InterpolatorGenerator<num, num> interpolator) {
+ Interpolator<num> u = uninterpolator(domain[0], domain[1]);
+ Interpolator<num> i = interpolator(range[0], range[1]);
+ return (num x) => i(u(x));
}
/// Returns a Function that given a value x on the domain, returns the
@@ -176,9 +183,15 @@
/// @param range The range of the scale.
/// @param uninterpolator The uninterpolator for domain values.
/// @param interpolator The interpolator for range values.
- static Function polylinearScale(
- List<num> domain, List range, Function uninterpolator, Function interpolator) {
- var u = [], i = [], j = 0, k = math.min(domain.length, range.length) - 1;
+ static num Function(num) polylinearScale(
+ List<num> domain,
+ List<num> range,
+ InterpolatorGenerator<num, num> uninterpolator,
+ InterpolatorGenerator<num, num> interpolator) {
+ List<Interpolator<num>> u = [];
+ List<Interpolator<num>> i = [];
+ int j = 0;
+ int k = math.min(domain.length, range.length) - 1;
// Handle descending domains.
if (domain[k] < domain[0]) {
@@ -191,7 +204,7 @@
i.add(interpolator(range[j - 1], range[j]));
}
- return (x) {
+ return (num x) {
int index = bisect(domain, x, 1, k) - 1;
return i[index](u[index](x));
};
@@ -233,5 +246,5 @@
return lo;
}
- static Function bisect = bisectRight;
+ static int Function(List<num>, num, [int, int]) bisect = bisectRight;
}
diff --git a/packages/charted/lib/core/scales/linear_scale.dart b/packages/charted/lib/core/scales/linear_scale.dart
index f9b4c13..2a5a387 100644
--- a/packages/charted/lib/core/scales/linear_scale.dart
+++ b/packages/charted/lib/core/scales/linear_scale.dart
@@ -7,27 +7,40 @@
//
part of charted.core.scales;
-class LinearScale implements Scale {
- static const defaultDomain = const [0, 1];
- static const defaultRange = const [0, 1];
+class LinearScale extends BaseLinearScale {
+ LinearScale();
+ LinearScale._clone(LinearScale source) : super._clone(source);
+
+ @override
+ Iterable<num> get ticks => _linearTickRange();
+
+ @override
+ LinearScale clone() => new LinearScale._clone(this);
+}
+
+abstract class BaseLinearScale implements Scale<num, num> {
+ static const List<int> defaultDomain = const [0, 1];
+ static const List<int> defaultRange = const [0, 1];
bool _rounded = false;
- Iterable _domain = defaultDomain;
- Iterable _range = defaultRange;
+ Iterable<num> _domain;
+ Iterable<num> _range;
int _ticksCount = 5;
int _forcedTicksCount = -1;
bool _clamp = false;
bool _nice = false;
- Function _invert;
- Function _scale;
+ num Function(num) _invert;
+ num Function(num) _scale;
- LinearScale();
+ BaseLinearScale()
+ : _domain = defaultDomain,
+ _range = defaultRange;
- LinearScale._clone(LinearScale source)
- : _domain = source._domain.toList(),
- _range = source._range.toList(),
+ BaseLinearScale._clone(BaseLinearScale source)
+ : _domain = new List<num>.from(source._domain),
+ _range = new List<num>.from(source._range),
_ticksCount = source._ticksCount,
_clamp = source._clamp,
_nice = source._nice,
@@ -38,7 +51,7 @@
void _reset({bool nice: false}) {
if (nice) {
_domain = ScaleUtils.nice(
- _domain as List<num>, ScaleUtils.niceStep(_linearTickRange().step));
+ _domain, ScaleUtils.niceStep(_linearTickRange().step));
} else {
if (_forcedTicksCount > 0) {
var tickRange = _linearTickRange();
@@ -46,41 +59,39 @@
}
}
- Function linear = math.min(_domain.length, _range.length) > 2
- ? ScaleUtils.polylinearScale
- : ScaleUtils.bilinearScale;
+ num Function(num) Function(List<num>, List<num>,
+ InterpolatorGenerator<num, num>, InterpolatorGenerator<num, num>)
+ linear = math.min(_domain.length, _range.length) > 2
+ ? ScaleUtils.polylinearScale
+ : ScaleUtils.bilinearScale;
- Function uninterpolator = clamp ? uninterpolateClamp : uninterpolateNumber;
- InterpolatorGenerator interpolator;
- if (rounded) {
- interpolator = createRoundedNumberInterpolator;
- } else {
- interpolator = createNumberInterpolator;
- }
+ InterpolatorGenerator<num, num> uninterpolator =
+ clamp ? uninterpolateClamp : uninterpolateNumber;
+ InterpolatorGenerator<num, num> interpolator =
+ rounded ? createRoundedNumberInterpolator : createNumberInterpolator;
- _invert = linear(_range, _domain, uninterpolator, createNumberInterpolator)
- as Function;
- _scale = linear(_domain, _range, uninterpolator, interpolator) as Function;
+ _invert = linear(_range, _domain, uninterpolator, createNumberInterpolator);
+ _scale = linear(_domain, _range, uninterpolator, interpolator);
}
@override
- set range(Iterable value) {
+ set range(Iterable<num> value) {
assert(value != null);
_range = value;
_reset();
}
@override
- Iterable get range => _range;
+ Iterable<num> get range => _range;
@override
- set domain(Iterable value) {
+ set domain(Iterable<num> value) {
_domain = value;
_reset(nice: _nice);
}
@override
- Iterable get domain => _domain;
+ Iterable<num> get domain => _domain;
@override
set rounded(bool value) {
@@ -106,15 +117,14 @@
@override
int get ticksCount => _ticksCount;
+ @override
set forcedTicksCount(int value) {
_forcedTicksCount = value;
_reset(nice: false);
}
- get forcedTicksCount => _forcedTicksCount;
-
@override
- Iterable get ticks => _linearTickRange();
+ int get forcedTicksCount => _forcedTicksCount;
@override
set clamp(bool value) {
@@ -141,18 +151,16 @@
bool get nice => _nice;
@override
- Extent get rangeExtent => ScaleUtils.extent(_range as Iterable<num>);
+ Extent<num> get rangeExtent => ScaleUtils.extent(_range);
@override
- scale(value) => _scale(value);
+ num scale(num value) => _scale(value);
@override
- invert(value) => _invert(value);
+ num invert(num value) => _invert(value);
- Range _linearTickRange([Extent extent]) {
- if (extent == null) {
- extent = ScaleUtils.extent(_domain as Iterable<num>);
- }
+ Range _linearTickRange([Extent<num> extent]) {
+ extent ??= ScaleUtils.extent(_domain);
num span = extent.max - extent.min;
if (span == 0) {
span = 1.0; // [span / _ticksCount] should never be equal zero.
@@ -166,18 +174,23 @@
// when forcing the ticks count at least the two ends of the scale would
// look nice and has a high chance of having the intermediate tick values
// to be nice.
- var maxFactor = extent.max == 0 ? 1
- : math.pow(10, (math.log((extent.max as num).abs() / forcedTicksCount)
- / math.LN10).floor());
+ var maxFactor = extent.max == 0
+ ? 1
+ : math.pow(
+ 10,
+ (math.log(extent.max.abs() / forcedTicksCount) / math.LN10)
+ .floor());
num max = (extent.max / maxFactor).ceil() * maxFactor;
- num minFactor = extent.min == 0 ? 1
- : math.pow(10, (math.log((extent.min as num).abs() / forcedTicksCount)
- / math.LN10).floor());
+ num minFactor = extent.min == 0
+ ? 1
+ : math.pow(
+ 10,
+ (math.log(extent.min.abs() / forcedTicksCount) / math.LN10)
+ .floor());
num min = (extent.min / minFactor).floor() * minFactor;
step = (max - min) / forcedTicksCount;
return new Range(min, max + step * 0.5, step);
} else {
-
step = math.pow(10, (math.log(span / _ticksCount) / math.LN10).floor());
var err = _ticksCount / span * step;
@@ -191,23 +204,20 @@
}
}
- return new Range(((extent.min as num) / step).ceil() * step,
- ((extent.max as num) / step).floor() * step + step * 0.5, step);
+ return new Range((extent.min / step).ceil() * step,
+ (extent.max / step).floor() * step + step * 0.5, step);
}
@override
FormatFunction createTickFormatter([String formatStr]) {
- int precision(num value) {
- return -(math.log(value) / math.LN10 + .01).floor();
- }
- Range tickRange = _linearTickRange();
if (formatStr == null) {
+ int precision(num value) {
+ return -(math.log(value) / math.LN10 + .01).floor();
+ }
+
+ Range tickRange = _linearTickRange();
formatStr = ".${precision(tickRange.step)}f";
}
- NumberFormat formatter = new NumberFormat(new EnUsLocale());
- return formatter.format(formatStr);
+ return Scale.numberFormatter.format(formatStr);
}
-
- @override
- LinearScale clone() => new LinearScale._clone(this);
}
diff --git a/packages/charted/lib/core/scales/log_scale.dart b/packages/charted/lib/core/scales/log_scale.dart
index c9f9f91..71d2a93 100644
--- a/packages/charted/lib/core/scales/log_scale.dart
+++ b/packages/charted/lib/core/scales/log_scale.dart
@@ -17,7 +17,7 @@
/// As log(0) is negative infinity, a log scale must have either an
/// exclusively-positive or exclusively-negative domain; the domain must not
/// include or cross zero.
-class LogScale implements Scale {
+class LogScale implements Scale<num, num> {
static const defaultBase = 10;
static const defaultDomain = const [1, 10];
static final negativeNumbersRoundFunctionsPair =
@@ -43,7 +43,7 @@
num _log(num x) =>
(_positive ? math.log(x < 0 ? 0 : x) : -math.log(x > 0 ? 0 : -x)) /
- math.log(base);
+ math.log(base);
num _pow(num x) => _positive ? math.pow(base, x) : -math.pow(base, -x);
@@ -57,28 +57,28 @@
int get base => _base;
@override
- num scale(x) => _linear.scale(_log(x as num)) as num;
+ num scale(num x) => _linear.scale(_log(x));
@override
- num invert(x) => _pow(_linear.invert(x as num) as num);
+ num invert(num x) => _pow(_linear.invert(x));
@override
- set domain(Iterable values) {
- _positive = (values.first as num) >= 0;
- _domain = values as List<num>;
+ set domain(covariant List<num> values) {
+ _positive = values.first >= 0;
+ _domain = values;
_reset();
}
@override
- Iterable get domain => _domain;
+ Iterable<num> get domain => _domain;
@override
- set range(Iterable newRange) {
+ set range(Iterable<num> newRange) {
_linear.range = newRange;
}
@override
- Iterable get range => _linear.range;
+ Iterable<num> get range => _linear.range;
@override
set rounded(bool value) {
@@ -121,7 +121,7 @@
@override
Extent get rangeExtent => _linear.rangeExtent;
- _reset() {
+ void _reset() {
if (_nice) {
var niced = _domain.map((num e) => _log(e)).toList();
var roundFunctions = _positive
@@ -135,11 +135,11 @@
}
}
- Iterable get ticks {
- var extent = ScaleUtils.extent(_domain),
- ticks = <num>[];
- num u = extent.min,
- v = extent.max;
+ @override
+ Iterable<num> get ticks {
+ var extent = ScaleUtils.extent(_domain);
+ var ticks = <num>[];
+ num u = extent.min, v = extent.max;
int i = (_log(u)).floor(),
j = (_log(v)).ceil(),
n = (_base % 1 > 0) ? 2 : _base;
@@ -159,13 +159,14 @@
return ticks;
}
+ @override
FormatFunction createTickFormatter([String formatStr]) {
- NumberFormat formatter = new NumberFormat(new EnUsLocale());
FormatFunction logFormatFunction =
- formatter.format(formatStr != null ? formatStr : ".0E");
+ Scale.numberFormatter.format(formatStr != null ? formatStr : ".0E");
var k = math.max(.1, ticksCount / this.ticks.length),
e = _positive ? 1e-12 : -1e-12;
- return (num d) {
+ return (dynamic _d) {
+ var d = _d as num;
if (_positive) {
return d / _pow((_log(d) + e).ceil()) <= k ? logFormatFunction(d) : '';
} else {
diff --git a/packages/charted/lib/core/scales/ordinal_scale.dart b/packages/charted/lib/core/scales/ordinal_scale.dart
index af1a11b..376856a 100644
--- a/packages/charted/lib/core/scales/ordinal_scale.dart
+++ b/packages/charted/lib/core/scales/ordinal_scale.dart
@@ -7,18 +7,20 @@
//
part of charted.core.scales;
-class _OrdinalScale implements OrdinalScale {
- final _index = new Map<dynamic, int>();
-
- List _domain = [];
- List _range = [];
+class _OrdinalScale<TDomain extends Comparable, TRange>
+ implements OrdinalScale<TDomain, TRange> {
+ final Map<TDomain, int> _index = {};
+ final List<TDomain> _domain;
+ List<TRange> _range;
num _rangeBand = 0;
- Extent _rangeExtent;
- Function _reset;
+ Extent<TDomain> _rangeExtent;
+ void Function(_OrdinalScale<TDomain, TRange>) _reset;
- _OrdinalScale();
+ _OrdinalScale()
+ : _domain = [],
+ _range = [];
- _OrdinalScale._clone(_OrdinalScale source)
+ _OrdinalScale._clone(_OrdinalScale<TDomain, TRange> source)
: _domain = new List.from(source._domain),
_range = new List.from(source._range),
_reset = source._reset,
@@ -28,18 +30,18 @@
}
@override
- scale(dynamic value) {
+ TRange scale(TDomain value) {
if (!_index.containsKey(value)) {
_index[value] = domain.length;
_domain.add(value);
}
return _range.isNotEmpty
? _range.elementAt(_index[value] % _range.length)
- : 0;
+ : null;
}
@override
- dynamic invert(value) {
+ TDomain invert(TRange value) {
int position = _range.indexOf(value);
return position > -1 && position < _domain.length
? _domain[position]
@@ -47,12 +49,11 @@
}
@override
- set domain(Iterable values) {
- _domain = [];
+ set domain(Iterable<TDomain> values) {
+ _domain.clear();
_index.clear();
- for (var i = 0; i < values.length; i++) {
- var value = values.elementAt(i);
+ for (var value in values) {
if (_index[value] == null) {
_index[value] = _domain.length;
_domain.add(value);
@@ -63,39 +64,44 @@
}
@override
- Iterable get domain => _domain;
+ Iterable<TDomain> get domain => _domain;
@override
- set range(Iterable values) => _setRange(this, values);
+ set range(Iterable<TRange> values) {
+ _reset = (_OrdinalScale<TDomain, TRange> s) {
+ s._range = new List<TRange>.from(values);
+ s._rangeBand = 0;
+ s._rangeExtent = null;
+ };
+ _reset(this);
+ }
@override
- Iterable get range => _range;
+ Iterable<TRange> get range => _range;
@override
- Extent get rangeExtent => _rangeExtent;
+ Extent<TDomain> get rangeExtent => _rangeExtent;
@override
- void rangePoints(Iterable range, [double padding = 0.0]) =>
+ void rangePoints(Iterable<num> range, [double padding = 0.0]) =>
_setRangePoints(this, range, padding);
@override
- void rangeBands(Iterable range,
+ void rangeBands(Iterable<num> range,
[double padding = 0.0, double outerPadding]) =>
- _setRangeBands(
- this, range, padding, outerPadding == null ? padding : outerPadding);
+ _setRangeBands(this, range, padding, outerPadding ?? padding);
@override
- void rangeRoundBands(Iterable range,
+ void rangeRoundBands(Iterable<num> range,
[double padding = 0.0, double outerPadding]) =>
- _setRangeRoundBands(
- this, range, padding, outerPadding == null ? padding : outerPadding);
+ _setRangeRoundBands(this, range, padding, outerPadding ?? padding);
@override
num get rangeBand => _rangeBand;
@override
FormatFunction createTickFormatter([String format]) =>
- (String s) => identityFunction/*<String>*/(s);
+ (dynamic s) => s.toString();
@override
Iterable get ticks => _domain;
@@ -103,31 +109,22 @@
@override
OrdinalScale clone() => new _OrdinalScale._clone(this);
- List _steps(start, step) =>
+ List _steps(num start, num step) =>
new Range(domain.length).map((num i) => start + step * i).toList();
- static void _setRange(_OrdinalScale scale, Iterable values) {
- scale._reset = (_OrdinalScale s) {
- s._range = new List.from(values);
- s._rangeBand = 0;
- s._rangeExtent = null;
- };
- scale._reset(scale);
- }
-
static void _setRangePoints(
- _OrdinalScale scale, Iterable range, double padding) {
+ _OrdinalScale scale, Iterable<num> range, double padding) {
scale._reset = (_OrdinalScale s) {
- var start = range.first,
- stop = range.last,
- step = s.domain.length > 1
- ? (stop - start - 2 * padding) / (s.domain.length - 1)
- : 0;
+ var start = range.first;
+ var stop = range.last;
+ var step = s.domain.length > 1
+ ? (stop - start - 2 * padding) / (s.domain.length - 1)
+ : 0;
s._range = s._steps(
s.domain.length < 2 ? (start + stop) / 2 : start + padding, step);
s._rangeBand = 0;
- s._rangeExtent = new Extent(start, stop);
+ s._rangeExtent = new Extent<num>(start, stop);
};
if (scale.domain.isNotEmpty) {
scale._reset(scale);
@@ -140,33 +137,29 @@
num start = range.first,
stop = range.last,
step = (stop - start - 2 * outerPadding) /
- (s.domain.length > 1
- ? (s.domain.length - padding)
- : 1);
+ (s.domain.length > 1 ? (s.domain.length - padding) : 1);
s._range = s._steps(start + step * outerPadding, step);
s._rangeBand = step * (1 - padding);
- s._rangeExtent = new Extent(start, stop);
+ s._rangeExtent = new Extent<num>(start, stop);
};
if (scale.domain.isNotEmpty) {
scale._reset(scale);
}
}
- static void _setRangeRoundBands(_OrdinalScale scale, Iterable range,
+ static void _setRangeRoundBands(_OrdinalScale scale, Iterable<num> range,
double padding, double outerPadding) {
scale._reset = (_OrdinalScale s) {
num start = range.first,
stop = range.last,
- step =
- ((stop - start - 2 * outerPadding) /
- (s.domain.length > 1
- ? (s.domain.length - padding)
- : 1)).floor();
+ step = ((stop - start - 2 * outerPadding) /
+ (s.domain.length > 1 ? (s.domain.length - padding) : 1))
+ .floor();
s._range = s._steps(start + outerPadding, step);
s._rangeBand = (step * (1 - padding)).round();
- s._rangeExtent = new Extent(start, stop);
+ s._rangeExtent = new Extent<num>(start, stop);
};
if (scale.domain.isNotEmpty) {
scale._reset(scale);
diff --git a/packages/charted/lib/core/scales/time_scale.dart b/packages/charted/lib/core/scales/time_scale.dart
index df07383..cc7785e 100644
--- a/packages/charted/lib/core/scales/time_scale.dart
+++ b/packages/charted/lib/core/scales/time_scale.dart
@@ -9,7 +9,7 @@
part of charted.core.scales;
/// TimeScale is a linear scale that operates on time.
-class TimeScale extends LinearScale {
+class TimeScale extends BaseLinearScale {
static const _scaleSteps = const [
1e3, // 1-second
5e3, // 5-second
@@ -52,7 +52,7 @@
[TimeInterval.year, 1]
];
- static TimeFormatFunction _scaleLocalFormat = new TimeFormat().multi([
+ static final FormatFunction _scaleLocalFormat = new TimeFormat().multi([
[".%L", (DateTime d) => d.millisecond > 0],
[":%S", (DateTime d) => d.second > 0],
["%I:%M", (DateTime d) => d.minute > 0],
@@ -67,13 +67,14 @@
TimeScale._clone(TimeScale source) : super._clone(source);
@override
- scale(Object val) =>
+ num scale(Object val) =>
super.scale(val is DateTime ? val.millisecondsSinceEpoch : val);
@override
- set domain(Iterable value) {
- super.domain =
- value.map((d) => d is DateTime ? d.millisecondsSinceEpoch : d).toList();
+ set domain(Iterable<dynamic> value) {
+ super.domain = value
+ .map<num>((d) => d is DateTime ? d.millisecondsSinceEpoch : d as num)
+ .toList();
}
@override
@@ -82,7 +83,7 @@
@override
TimeScale clone() => new TimeScale._clone(this);
- List _getTickMethod(Extent extent, int count) {
+ List<dynamic> _getTickMethod(Extent<num> extent, int count) {
num target = (extent.max - extent.min) / count;
int i = ScaleUtils.bisect(_scaleSteps, target);
@@ -90,42 +91,43 @@
? [
TimeInterval.year,
_linearTickRange(
- new Extent(extent.min / 31536e6, extent.max / 31536e6)).step
+ new Extent<num>(extent.min / 31536e6, extent.max / 31536e6))
+ .step
]
: i == 0
? [new ScaleMilliSeconds(), _linearTickRange(extent).step]
- : _scaleLocalMethods[target / _scaleSteps[i - 1] <
- _scaleSteps[i] / target ? i - 1 : i];
+ : _scaleLocalMethods[
+ target / _scaleSteps[i - 1] < _scaleSteps[i] / target
+ ? i - 1
+ : i];
}
- List niceInterval(int ticksCount, [int skip = 1]) {
- var extent = ScaleUtils.extent(domain as Iterable<num>),
- method = _getTickMethod(extent, ticksCount),
- interval;
+ List<num> niceInterval(int ticksCount) {
+ var extent = ScaleUtils.extent(domain);
+ var method = _getTickMethod(extent, ticksCount);
+ TimeInterval interval = method[0];
+ int skip = method[1];
- if (method != null) {
- interval = method[0];
- skip = method[1] as int;
- }
-
- bool skipped(var date) {
- if (date is DateTime) date = date.millisecondsSinceEpoch;
- return (interval as TimeInterval).range(date, date + 1, skip).length == 0;
+ bool skipped(DateTime date) {
+ var seconds = date.millisecondsSinceEpoch;
+ return interval.range(seconds, seconds + 1, skip).length == 0;
}
if (skip > 1) {
domain = ScaleUtils.nice(
domain as List<num>,
new RoundingFunctions((dateMillis) {
- var date = new DateTime.fromMillisecondsSinceEpoch(dateMillis.round());
- while (skipped(date = (interval as TimeInterval).floor(date))) {
+ var date =
+ new DateTime.fromMillisecondsSinceEpoch(dateMillis.round());
+ while (skipped(date = interval.floor(date))) {
date = new DateTime.fromMillisecondsSinceEpoch(
date.millisecondsSinceEpoch - 1);
}
return date.millisecondsSinceEpoch;
}, (dateMillis) {
- var date = new DateTime.fromMillisecondsSinceEpoch(dateMillis.round());
- while (skipped(date = (interval as TimeInterval).ceil(date))) {
+ var date =
+ new DateTime.fromMillisecondsSinceEpoch(dateMillis.round());
+ while (skipped(date = interval.ceil(date))) {
date = new DateTime.fromMillisecondsSinceEpoch(
date.millisecondsSinceEpoch + 1);
}
@@ -135,12 +137,10 @@
domain = ScaleUtils.nice(
domain as List<num>,
new RoundingFunctions(
- (date) => (interval as TimeInterval).
- floor(date).millisecondsSinceEpoch,
- (date) => (interval as TimeInterval).
- ceil(date).millisecondsSinceEpoch));
+ (date) => interval.floor(date).millisecondsSinceEpoch,
+ (date) => interval.ceil(date).millisecondsSinceEpoch));
}
- return domain as List;
+ return domain;
}
@override
@@ -152,44 +152,44 @@
}
}
- List ticksInterval(int ticksCount, [int skip]) {
- var extent = ScaleUtils.extent(domain as Iterable<num>),
- method = _getTickMethod(extent, ticksCount),
- interval;
- if (method != null) {
- interval = method[0];
- skip = method[1] as int;
- }
- return (interval as TimeInterval).range(extent.min, extent.max + 1,
- skip < 1 ? 1 : skip).toList();
+ List<DateTime> ticksInterval(int ticksCount) {
+ var extent = ScaleUtils.extent(domain);
+ var method = _getTickMethod(extent, ticksCount);
+ TimeInterval interval = method[0];
+ int skip = method[1];
+ return interval
+ .range(extent.min, extent.max + 1, skip < 1 ? 1 : skip)
+ .toList();
}
@override
- List get ticks => ticksInterval(ticksCount);
+ List<DateTime> get ticks => ticksInterval(ticksCount);
}
class ScaleMilliSeconds implements TimeInterval {
- DateTime _toDateTime(x) {
- assert(x is int || x is DateTime);
- return x is int ?
- new DateTime.fromMillisecondsSinceEpoch(x as int) : x as DateTime;
- }
-
DateTime floor(dynamic val) => _toDateTime(val);
DateTime ceil(dynamic val) => _toDateTime(val);
DateTime round(dynamic val) => _toDateTime(val);
DateTime offset(Object val, int dt) {
- assert(val is int || val is DateTime);
- return new DateTime.fromMillisecondsSinceEpoch(
- val is int ? val + dt : (val as DateTime).millisecondsSinceEpoch + dt);
+ return new DateTime.fromMillisecondsSinceEpoch(_toMilliseconds(val) + dt);
}
- List<DateTime> range(var t0, var t1, int step) {
- int start = t0 is DateTime ? t0.millisecondsSinceEpoch : t0,
- stop = t1 is DateTime ? t1.millisecondsSinceEpoch : t1;
+ List<DateTime> range(dynamic t0, dynamic t1, int step) {
+ int start = _toMilliseconds(t0);
+ int stop = _toMilliseconds(t1);
return new Range((start / step).ceil() * step, stop, step)
.map((d) => new DateTime.fromMillisecondsSinceEpoch(d as int))
.toList();
}
+
+ static DateTime _toDateTime(/* int | DateTime */ dynamic x) {
+ return x is int
+ ? new DateTime.fromMillisecondsSinceEpoch(x)
+ : x as DateTime;
+ }
+
+ static int _toMilliseconds(/* int | DateTime */ dynamic val) {
+ return val is int ? val : (val as DateTime).millisecondsSinceEpoch;
+ }
}
diff --git a/packages/charted/lib/core/time_interval.dart b/packages/charted/lib/core/time_interval.dart
index 962b0b2..fd3a858 100644
--- a/packages/charted/lib/core/time_interval.dart
+++ b/packages/charted/lib/core/time_interval.dart
@@ -8,14 +8,10 @@
library charted.core.time_intervals;
-typedef DateTime TimeFloorFunction(DateTime val);
-typedef DateTime TimeStepFunction(DateTime val, int offset);
-typedef int TimeToNumberFunction(DateTime val);
-
class TimeInterval {
- TimeFloorFunction _floor;
- TimeStepFunction _step;
- TimeToNumberFunction _number;
+ final DateTime Function(DateTime) _floor;
+ final DateTime Function(DateTime, int) _step;
+ final int Function(DateTime) _number;
TimeInterval(this._floor, this._step, this._number);
@@ -44,13 +40,13 @@
assert(t1 is int || t1 is DateTime);
List<DateTime> values = [];
- if (t1 is int) {
- t1 = new DateTime.fromMillisecondsSinceEpoch(t1 as int);
- }
+ DateTime t1Date = t1 is DateTime
+ ? t1
+ : new DateTime.fromMillisecondsSinceEpoch(t1 as int);
DateTime time = ceil(t0);
if (dt > 1) {
- while (time.isBefore(t1 as DateTime)) {
+ while (time.isBefore(t1Date)) {
if ((_number(time) % dt) == 0) {
values.add(new DateTime.fromMillisecondsSinceEpoch(
time.millisecondsSinceEpoch));
@@ -58,7 +54,7 @@
time = _step(time, 1);
}
} else {
- while (time.isBefore(t1 as DateTime)) {
+ while (time.isBefore(t1Date)) {
values.add(new DateTime.fromMillisecondsSinceEpoch(
time.millisecondsSinceEpoch));
time = _step(time, 1);
@@ -67,31 +63,31 @@
return values;
}
- static TimeInterval second = new TimeInterval(
+ static final TimeInterval second = new TimeInterval(
(DateTime date) => new DateTime.fromMillisecondsSinceEpoch(
(date.millisecondsSinceEpoch ~/ 1000) * 1000),
- (DateTime date, int offset) =>
- date = new DateTime.fromMillisecondsSinceEpoch(
+ (DateTime date, int offset) => date =
+ new DateTime.fromMillisecondsSinceEpoch(
date.millisecondsSinceEpoch + offset * 1000),
(DateTime date) => date.second);
- static TimeInterval minute = new TimeInterval(
+ static final TimeInterval minute = new TimeInterval(
(DateTime date) => new DateTime.fromMillisecondsSinceEpoch(
(date.millisecondsSinceEpoch ~/ 60000) * 60000),
- (DateTime date, int offset) =>
- date = new DateTime.fromMillisecondsSinceEpoch(
+ (DateTime date, int offset) => date =
+ new DateTime.fromMillisecondsSinceEpoch(
date.millisecondsSinceEpoch + offset * 60000),
(DateTime date) => date.minute);
- static TimeInterval hour = new TimeInterval(
+ static final TimeInterval hour = new TimeInterval(
(DateTime date) => new DateTime.fromMillisecondsSinceEpoch(
(date.millisecondsSinceEpoch ~/ 3600000) * 3600000),
- (DateTime date, int offset) =>
- date = new DateTime.fromMillisecondsSinceEpoch(
+ (DateTime date, int offset) => date =
+ new DateTime.fromMillisecondsSinceEpoch(
date.millisecondsSinceEpoch + offset * 3600000),
(DateTime date) => date.hour);
- static TimeInterval day = new TimeInterval(
+ static final TimeInterval day = new TimeInterval(
(DateTime date) => new DateTime(date.year, date.month, date.day),
(DateTime date, int offset) => new DateTime(
date.year,
@@ -103,7 +99,7 @@
date.millisecond),
(DateTime date) => date.day - 1);
- static TimeInterval week = new TimeInterval(
+ static final TimeInterval week = new TimeInterval(
(DateTime date) =>
new DateTime(date.year, date.month, date.day - (date.weekday % 7)),
(DateTime date, int offset) => new DateTime(
@@ -118,7 +114,7 @@
return (dayOfYear(date) + day % 7) ~/ 7;
});
- static TimeInterval month = new TimeInterval(
+ static final TimeInterval month = new TimeInterval(
(DateTime date) => new DateTime(date.year, date.month, 1),
(DateTime date, int offset) => new DateTime(
date.year,
@@ -130,7 +126,7 @@
date.millisecond),
(DateTime date) => date.month - 1);
- static TimeInterval year = new TimeInterval(
+ static final TimeInterval year = new TimeInterval(
(DateTime date) => new DateTime(date.year),
(DateTime date, int offset) => new DateTime(
date.year + offset,
diff --git a/packages/charted/lib/core/utils.dart b/packages/charted/lib/core/utils.dart
index d153a34..16729cf 100644
--- a/packages/charted/lib/core/utils.dart
+++ b/packages/charted/lib/core/utils.dart
@@ -31,11 +31,8 @@
const String ORIENTATION_TOP = 'top';
const String ORIENTATION_BOTTOM = 'bottom';
-/// Identity function that returns the value passed as it's parameter.
-/*=T*/ identityFunction/*<T>*/(/*=T*/x) => x;
-
/// Function that formats a value to String.
-typedef String FormatFunction(value);
+typedef FormatFunction = String Function(dynamic);
/// Test if the given String or Iterable, [val] is null or empty
bool isNullOrEmpty(val) {
diff --git a/packages/charted/lib/core/utils/color.dart b/packages/charted/lib/core/utils/color.dart
index 5f80759..c2ae5a5 100644
--- a/packages/charted/lib/core/utils/color.dart
+++ b/packages/charted/lib/core/utils/color.dart
@@ -223,6 +223,7 @@
? "0" + math.max(0, v).toInt().toRadixString(16)
: math.min(255, v).toInt().toRadixString(16);
}
+
return '#${_hexify(r)}${_hexify(g)}${_hexify(b)}';
}
diff --git a/packages/charted/lib/core/utils/lists.dart b/packages/charted/lib/core/utils/lists.dart
index b272ccb..16fcffc 100644
--- a/packages/charted/lib/core/utils/lists.dart
+++ b/packages/charted/lib/core/utils/lists.dart
@@ -9,7 +9,7 @@
part of charted.core.utils;
/// Returns a sum of all values in the given list of values
-num sum(List values) => values == null || values.isEmpty
+num sum(List<num> values) => values == null || values.isEmpty
? 0
: values.fold(0.0, (num old, num next) => old + next);
@@ -37,12 +37,12 @@
}
/// Represents a pair of mininum and maximum values in a List.
-class Extent<T> extends Pair<T, T> {
- final T min;
- final T max;
+class Extent<T extends Comparable> extends Pair<T, T> {
+ T get min => first;
+ T get max => last;
factory Extent.items(Iterable<T> items,
- [Comparator compare = Comparable.compare]) {
+ [Comparator<T> compare = Comparable.compare]) {
if (items.length == 0) return new Extent(null, null);
var max = items.first, min = items.first;
for (var value in items) {
@@ -53,9 +53,7 @@
}
const Extent(T min, T max)
- : min = min,
- max = max,
- super(min, max);
+ : super(min, max);
}
/// Iterable representing a range of values containing the start, stop
diff --git a/packages/charted/lib/core/utils/rect.dart b/packages/charted/lib/core/utils/rect.dart
index beaa2cc..2fb8bae 100644
--- a/packages/charted/lib/core/utils/rect.dart
+++ b/packages/charted/lib/core/utils/rect.dart
@@ -68,7 +68,8 @@
const AbsoluteRect(this.top, this.end, this.bottom, this.start);
@override
- bool operator ==(other) => other is AbsoluteRect &&
+ bool operator ==(other) =>
+ other is AbsoluteRect &&
start == other.start &&
end == other.end &&
top == other.top &&
diff --git a/packages/charted/lib/layout/src/hierarchy_layout.dart b/packages/charted/lib/layout/src/hierarchy_layout.dart
index d3a4c59..0bb8933 100644
--- a/packages/charted/lib/layout/src/hierarchy_layout.dart
+++ b/packages/charted/lib/layout/src/hierarchy_layout.dart
@@ -30,8 +30,8 @@
List rows, int parentColumn, int labelColumn, int valueColumn) {
List<T> nodeList = [];
for (var row in rows) {
- nodeList.add(createNode(row[labelColumn] as String,
- row[valueColumn] as num, 0));
+ nodeList.add(
+ createNode(row[labelColumn] as String, row[valueColumn] as num, 0));
}
for (var i = 0; i < rows.length; i++) {
diff --git a/packages/charted/lib/layout/src/pie_layout.dart b/packages/charted/lib/layout/src/pie_layout.dart
index d66d992..5d56e04 100644
--- a/packages/charted/lib/layout/src/pie_layout.dart
+++ b/packages/charted/lib/layout/src/pie_layout.dart
@@ -44,13 +44,14 @@
*/
List<SvgArcData> layout(List<num> data, [int ei, Element e]) {
var values =
- new List.generate(data.length, (int i) => accessor(data[i], i)),
+ new List.generate(data.length, (int i) => accessor(data[i], i)),
startAngle = startAngleCallback(data, ei, e),
endAngle = endAngleCallback(data, ei, e),
total = sum(values),
scaleFactor = (endAngle - startAngle) / (total > 0 ? total : 1),
arcs = new List<SvgArcData>(data.length);
- List<int> index = new Range.integers(values.length).toList();
+ List<int> index =
+ new Range.integers(values.length).map<int>((v) => v as int).toList();
if (compare != null) {
index.sort((left, right) => compare(data[left], data[right]));
@@ -77,7 +78,7 @@
}
/** Default value accessor */
- static num defaultValueAccessor(num d, i) => d;
+ static num defaultValueAccessor(d, int i) => d as num;
/** Default start angle callback - returns 0 */
static num defaultStartAngleCallback(d, i, _) => 0;
diff --git a/packages/charted/lib/layout/src/treemap_layout.dart b/packages/charted/lib/layout/src/treemap_layout.dart
index 56c3d48..4a01020 100644
--- a/packages/charted/lib/layout/src/treemap_layout.dart
+++ b/packages/charted/lib/layout/src/treemap_layout.dart
@@ -111,7 +111,7 @@
}
/// Applies padding between each nodes.
- MutableRect _treeMapPad(TreeMapNode node, List<num>padding) {
+ MutableRect _treeMapPad(TreeMapNode node, List<num> padding) {
var x = node.x + padding[3];
var y = node.y + padding.first;
var dx = node.dx - padding[1] - padding[3];
@@ -138,9 +138,7 @@
/// Computes the most amount of area needed to layout the list of nodes.
num _worst(List<TreeMapNode> nodes, num length, num pArea) {
- num area,
- rmax = 0,
- rmin = double.INFINITY;
+ num area, rmax = 0, rmin = double.INFINITY;
for (var node in nodes) {
area = node.area;
if (area <= 0) continue;
diff --git a/packages/charted/lib/locale/format.dart b/packages/charted/lib/locale/format.dart
index a41afd9..0ecbee6 100644
--- a/packages/charted/lib/locale/format.dart
+++ b/packages/charted/lib/locale/format.dart
@@ -28,10 +28,8 @@
*
* @see <a href="http://docs.python.org/release/3.1.3/library/string.html#formatspec">Python format specification mini-language</a>
*/
-FormatFunction format(String specifier, [Locale locale = null]) {
- if (locale == null) {
- locale = new EnUsLocale();
- }
+FormatFunction format(String specifier, [Locale locale]) {
+ locale ??= new EnUsLocale();
return locale.numberFormat.format(specifier);
}
@@ -40,7 +38,7 @@
*/
class FormatPrefix {
// SI scale units in increments of 1000.
- static const List<String> unitPrefixes = const [
+ static const List<String> _unitPrefixes = const [
"y",
"z",
"a",
@@ -60,7 +58,7 @@
"Y"
];
- Function _scale;
+ num Function(num) _scale;
String _symbol;
FormatPrefix(num value, [int precision = 0]) {
@@ -80,7 +78,7 @@
// Sets the scale and symbol of the value.
var k = math.pow(10, (8 - i).abs() * 3);
_scale = i > 8 ? (d) => d / k : (d) => d * k;
- _symbol = unitPrefixes[i];
+ _symbol = _unitPrefixes[i];
}
num _formatPrecision(num x, num p) {
@@ -93,8 +91,8 @@
}
/** Returns the SI prefix for the value. */
- get symbol => _symbol;
+ String get symbol => _symbol;
/** Returns the scale for the value corresponding to the SI prefix. */
- get scale => _scale;
+ num Function(num) get scale => _scale;
}
diff --git a/packages/charted/lib/locale/format/number_format.dart b/packages/charted/lib/locale/format/number_format.dart
index f53e933..4008e53 100644
--- a/packages/charted/lib/locale/format/number_format.dart
+++ b/packages/charted/lib/locale/format/number_format.dart
@@ -15,41 +15,21 @@
*/
class NumberFormat {
// [[fill]align][sign][symbol][0][width][,][.precision][type]
- static RegExp FORMAT_REGEX = new RegExp(
+ static final RegExp FORMAT_REGEX = new RegExp(
r'(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?'
r'(\.-?\d+)?([a-z%])?',
caseSensitive: false);
- String localeDecimal;
- String localeThousands;
- List<int> localeGrouping;
- List<String> localeCurrency;
- Function formatGroup;
+ final String _localeDecimal;
+ final List<int> _localeGrouping;
+ final String _localeThousands;
+ final List<String> _localeCurrency;
- NumberFormat(Locale locale) {
- localeDecimal = locale.decimal;
- localeThousands = locale.thousands;
- localeGrouping = locale.grouping;
- localeCurrency = locale.currency;
- formatGroup = (localeGrouping != null)
- ? (String value) {
- int i = value.length, j = 0, g = localeGrouping[0];
- var t = [];
- while (i > 0 && g > 0) {
- if (i - g >= 0) {
- i = i - g;
- } else {
- g = i;
- i = 0;
- }
- var length = (i + g) < value.length ? (i + g) : value.length;
- t.add(value.substring(i, length));
- g = localeGrouping[j = (j + 1) % localeGrouping.length];
- }
- return t.reversed.join(localeThousands);
- }
- : (x) => x;
- }
+ NumberFormat(Locale locale)
+ : _localeDecimal = locale.decimal,
+ _localeGrouping = locale.grouping,
+ _localeThousands = locale.thousands,
+ _localeCurrency = locale.currency;
/**
* Returns a new format function with the given string specifier. A format
@@ -70,8 +50,9 @@
zfill = match.group(5),
width = match.group(6) != null ? int.parse(match.group(6)) : 0,
comma = match.group(7) != null,
- precision =
- match.group(8) != null ? int.parse(match.group(8).substring(1)) : null,
+ precision = match.group(8) != null
+ ? int.parse(match.group(8).substring(1))
+ : null,
type = match.group(9),
scale = 1,
prefix = '',
@@ -119,8 +100,8 @@
}
if (symbol == '\$') {
- prefix = localeCurrency[0];
- suffix = localeCurrency[1];
+ prefix = _localeCurrency[0];
+ suffix = _localeCurrency[1];
}
// If no precision is specified for r, fallback to general notation.
@@ -141,7 +122,8 @@
var zcomma = (zfill != null) && comma;
- return (num value) {
+ return (dynamic _value) {
+ var value = _value as num;
if (value == null) return '-';
var fullSuffix = suffix;
@@ -163,7 +145,7 @@
if (scale < 0) {
FormatPrefix unit =
new FormatPrefix(value, (precision != null) ? precision : 0);
- value = unit.scale(value) as num;
+ value = unit.scale(value);
fullSuffix = unit.symbol + suffix;
} else {
value *= scale;
@@ -181,13 +163,12 @@
// (after).
int i = stringValue.lastIndexOf('.');
String before = i < 0 ? stringValue : stringValue.substring(0, i),
- after = i < 0 ? '' : localeDecimal + stringValue.substring(i + 1);
-
+ after = i < 0 ? '' : _localeDecimal + stringValue.substring(i + 1);
// If the fill character is not '0', grouping is applied before
//padding.
if (zfill == null && comma) {
- before = formatGroup(before) as String;
+ before = _formatGroup(before);
}
int length = prefix.length +
@@ -200,7 +181,7 @@
// If the fill character is '0', grouping is applied after padding.
if (zcomma) {
- before = formatGroup(padding + before) as String;
+ before = _formatGroup(padding + before);
}
// Apply prefix.
@@ -219,9 +200,9 @@
negative +
stringValue +
padding.substring(length)
- : negative + (zcomma
- ? stringValue
- : padding + stringValue)) + fullSuffix;
+ : negative +
+ (zcomma ? stringValue : padding + stringValue)) +
+ fullSuffix;
};
}
@@ -250,4 +231,24 @@
return (num x, [int p = 0]) => x.toString();
}
}
+
+ String _formatGroup(String value) {
+ if (_localeGrouping == null) {
+ return value;
+ }
+ int i = value.length, j = 0, g = _localeGrouping[0];
+ var t = <String>[];
+ while (i > 0 && g > 0) {
+ if (i - g >= 0) {
+ i = i - g;
+ } else {
+ g = i;
+ i = 0;
+ }
+ var length = (i + g) < value.length ? (i + g) : value.length;
+ t.add(value.substring(i, length));
+ g = _localeGrouping[j = (j + 1) % _localeGrouping.length];
+ }
+ return t.reversed.join(_localeThousands);
+ }
}
diff --git a/packages/charted/lib/locale/format/time_format.dart b/packages/charted/lib/locale/format/time_format.dart
index 89ab88c..5b15f80 100644
--- a/packages/charted/lib/locale/format/time_format.dart
+++ b/packages/charted/lib/locale/format/time_format.dart
@@ -7,25 +7,19 @@
*/
part of charted.locale.format;
-typedef String TimeFormatFunction(DateTime date);
-
//TODO(songrenchu): Document time format; Add test for time format.
class TimeFormat {
- String _template;
- String _locale;
- DateFormat _dateFormat;
+ final String _template;
+ final String _locale;
+ final DateFormat _dateFormat;
- TimeFormat([String template = null, String identifier = 'en_US']) {
- _template = template;
- _locale = identifier;
- if (_template != null) _dateFormat =
- new DateFormat(_wrapStrptime2ICU(_template), _locale);
- }
-
- TimeFormat _getInstance(String template) {
- return new TimeFormat(template, _locale);
- }
+ TimeFormat([String template, String identifier = 'en_US'])
+ : _template = template,
+ _locale = identifier,
+ _dateFormat = template == null
+ ? null
+ : new DateFormat(_wrapStrptime2ICU(template), identifier);
String apply(DateTime date) {
assert(_dateFormat != null);
@@ -39,35 +33,32 @@
return _dateFormat.parse(string);
}
- TimeFormatFunction multi(List<List> formats) {
+ FormatFunction multi(List<List> formats) {
var n = formats.length, i = -1;
- while (++i < n) formats[i][0] = _getInstance(formats[i][0] as String);
- return (var date) {
- if (date is num) {
- date = new DateTime.fromMillisecondsSinceEpoch((date as num).toInt());
- }
+ while (++i < n) {
+ formats[i][0] = new TimeFormat(formats[i][0] as String, _locale);
+ }
+ return (dynamic _date) {
+ DateTime date = _date is DateTime
+ ? _date
+ : new DateTime.fromMillisecondsSinceEpoch((_date as num).toInt());
var i = 0, f = formats[i];
- while (f.length < 2 || f[1](date) == false) {
+ while (f.length < 2 || !(f[1] as bool Function(DateTime))(date)) {
i++;
if (i < n) f = formats[i];
}
if (i == n) return null;
- return f[0].apply(date);
+ return (f[0] as TimeFormat).apply(date);
};
}
- UTCTimeFormat utc([String specifier = null]) {
- return new UTCTimeFormat(
- specifier == null ? _template : specifier, _locale);
- }
-
- static UTCTimeFormat iso() {
- return new UTCTimeFormat("%Y-%m-%dT%H:%M:%S.%LZ");
- }
-
- static Map timeFormatPads = {"-": "", "_": " ", "0": "0"};
+ static const Map<String, String> _timeFormatPads = const {
+ "-": "",
+ "_": " ",
+ "0": "0"
+ };
// TODO(songrenchu): Cannot fully be equivalent now.
- static Map timeFormatsTransform = {
+ static const Map<String, String> _timeFormatsTransform = const {
'a': 'EEE',
'A': 'EEEE',
'b': 'MMM',
@@ -96,33 +87,20 @@
'%': '%'
};
- String _wrapStrptime2ICU(String template) {
- var string = [], i = -1, j = 0, n = template.length, tempChar;
+ static String _wrapStrptime2ICU(String template) {
+ List<String> string = [];
+ int i = -1, j = 0, n = template.length;
while (++i < n) {
if (template[i] == '%') {
string.add(template.substring(j, i));
- if ((timeFormatPads[tempChar = template[++i]]) != null) tempChar =
- template[++i];
- if (timeFormatsTransform[tempChar] != null) string
- .add(timeFormatsTransform[tempChar]);
+ String ch = template[++i];
+ if ((_timeFormatPads[ch]) != null) ch = template[++i];
+ if (_timeFormatsTransform[ch] != null)
+ string.add(_timeFormatsTransform[ch]);
j = i + 1;
}
}
- if (j < i) string.add("'" + template.substring(j, i) + "'");
- return string.join("");
- }
-}
-
-class UTCTimeFormat extends TimeFormat {
- UTCTimeFormat(String template, [String identifier = 'en_US'])
- : super(template, identifier);
-
- UTCTimeFormat _getInstance(String template) {
- return new UTCTimeFormat(template, _locale);
- }
-
- DateTime parse(String string) {
- assert(_dateFormat != null);
- return _dateFormat.parseUTC(string);
+ if (j < i) string.add("'${template.substring(j, i)}'");
+ return string.join();
}
}
diff --git a/packages/charted/lib/locale/languages/en_us.dart b/packages/charted/lib/locale/languages/en_us.dart
index 7d6b354..6f5deb8 100644
--- a/packages/charted/lib/locale/languages/en_us.dart
+++ b/packages/charted/lib/locale/languages/en_us.dart
@@ -12,25 +12,32 @@
static EnUsLocale instance;
factory EnUsLocale() {
- if (EnUsLocale.instance == null) {
- EnUsLocale.instance = new EnUsLocale._create();
- }
- return EnUsLocale.instance;
+ return instance ??= new EnUsLocale._();
}
- EnUsLocale._create();
+ EnUsLocale._();
- final identifier = 'en_US';
- final decimal = '.';
- final thousands = ',';
- final grouping = const [3];
- final currency = const ['\$', ''];
- final dateTime = '%a %b %e %X %Y';
- final date = '%m/%d/%Y';
- final time = '%H =>%M =>%S';
- final periods = const ['AM', 'PM'];
+ @override
+ final String identifier = 'en_US';
+ @override
+ final String decimal = '.';
+ @override
+ final String thousands = ',';
+ @override
+ final List<int> grouping = const [3];
+ @override
+ final List<String> currency = const ['\$', ''];
+ @override
+ final String dateTime = '%a %b %e %X %Y';
+ @override
+ final String date = '%m/%d/%Y';
+ @override
+ final String time = '%H =>%M =>%S';
+ @override
+ final List<String> periods = const ['AM', 'PM'];
- final days = const [
+ @override
+ final List<String> days = const [
'Sunday',
'Monday',
'Tuesday',
@@ -39,9 +46,19 @@
'Friday',
'Saturday'
];
- final shortDays = const ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+ @override
+ final List<String> shortDays = const [
+ 'Sun',
+ 'Mon',
+ 'Tue',
+ 'Wed',
+ 'Thu',
+ 'Fri',
+ 'Sat'
+ ];
- final months = const [
+ @override
+ final List<String> months = const [
'January',
'February',
'March',
@@ -55,7 +72,8 @@
'November',
'December'
];
- final shortMonths = const [
+ @override
+ final List<String> shortMonths = const [
'Jan',
'Feb',
'Mar',
diff --git a/packages/charted/lib/locale/locale.dart b/packages/charted/lib/locale/locale.dart
index 2b22efd..9884d7a 100644
--- a/packages/charted/lib/locale/locale.dart
+++ b/packages/charted/lib/locale/locale.dart
@@ -22,17 +22,18 @@
String get dateTime;
String get date;
String get time;
- List get periods;
+ List<String> get periods;
- List get days;
- List get shortDays;
+ List<String> get days;
+ List<String> get shortDays;
- List get months;
- List get shortMonths;
+ List<String> get months;
+ List<String> get shortMonths;
Locale();
NumberFormat get numberFormat => new NumberFormat(this);
- TimeFormat timeFormat([String specifier = null]) =>
+
+ TimeFormat timeFormat([String specifier]) =>
new TimeFormat(specifier, this.identifier);
}
diff --git a/packages/charted/lib/selection/selection.dart b/packages/charted/lib/selection/selection.dart
index 92823bd..0d94977 100644
--- a/packages/charted/lib/selection/selection.dart
+++ b/packages/charted/lib/selection/selection.dart
@@ -37,7 +37,7 @@
typedef E SelectionValueAccessor<E>(datum, int index);
/** Create a ChartedCallback that always returns [val] */
-SelectionCallback/*<T>*/ toCallback/*<T>*/(/*=T*/ val) => (datum, index, element) => val;
+SelectionCallback<T> toCallback<T>(T val) => (datum, index, element) => val;
/** Create a ChartedValueAccessor that always returns [val] */
SelectionValueAccessor toValueAccessor(val) => (datum, index) => val;
diff --git a/packages/charted/lib/selection/src/selection_impl.dart b/packages/charted/lib/selection/src/selection_impl.dart
index eef281b..5e1e8d7 100644
--- a/packages/charted/lib/selection/src/selection_impl.dart
+++ b/packages/charted/lib/selection/src/selection_impl.dart
@@ -308,8 +308,9 @@
fn: (datum, ei, e) {
Element child = fn(datum, ei, e);
Element before = beforeFn(datum, ei, e);
- return child == null ? null : e.insertBefore(child, before)
- as Element;
+ return child == null
+ ? null
+ : e.insertBefore(child, before) as Element;
},
source: this);
}
@@ -343,6 +344,7 @@
scope.associate(element, val);
return element;
}
+
;
// Joins data to all elements in the group.
@@ -431,6 +433,7 @@
updateGroups.add(new _SelectionGroupImpl(update, parent: g.parent));
exitGroups.add(new _SelectionGroupImpl(exit, parent: g.parent));
}
+
;
for (int gi = 0; gi < groups.length; ++gi) {
diff --git a/packages/charted/lib/selection/src/transition_impl.dart b/packages/charted/lib/selection/src/transition_impl.dart
index a560e1e..e5b6d17 100644
--- a/packages/charted/lib/selection/src/transition_impl.dart
+++ b/packages/charted/lib/selection/src/transition_impl.dart
@@ -16,10 +16,10 @@
SelectionCallback _duration =
(d, i, c) => Transition.defaultDurationMilliseconds;
Selection _selection;
- Map _attrs = {};
- Map _styles = {};
- Map _attrTweens = {};
- Map _styleTweens = {};
+ Map<String, dynamic> _attrs = {};
+ Map<String, dynamic> _styles = {};
+ Map<String, dynamic> _attrTweens = {};
+ Map<String, dynamic> _styleTweens = {};
Map<AnimationTimer, Element> _timerMap = {};
Map<Element, List<Interpolator>> _attrMap = {};
Map<Element, int> _durationMap = {};
@@ -87,9 +87,8 @@
tweenList.add(_getAttrInterpolator(c, key, value(d, i, c)));
});
_attrTweens.forEach((String key, value) {
- tweenList.add(
- (t) => c.setAttribute(key,
- value(d, i, c.getAttribute(key))(t) as String));
+ tweenList.add((t) => c.setAttribute(
+ key, value(d, i, c.getAttribute(key))(t) as String));
});
_styles.forEach((String key, value) {
tweenList.add(_getStyleInterpolator(
diff --git a/packages/charted/lib/svg/axis.dart b/packages/charted/lib/svg/axis.dart
index 6adc9d4..2f9e879 100644
--- a/packages/charted/lib/svg/axis.dart
+++ b/packages/charted/lib/svg/axis.dart
@@ -54,9 +54,8 @@
: scale = scale == null ? new LinearScale() : scale {
_tickFormat =
tickFormat == null ? this.scale.createTickFormatter() : tickFormat;
- _tickValues = isNullOrEmpty(tickValues)
- ? this.scale.ticks.toList()
- : tickValues;
+ _tickValues =
+ isNullOrEmpty(tickValues) ? this.scale.ticks.toList() : tickValues;
}
Iterable get tickValues => _tickValues;
@@ -64,12 +63,12 @@
FormatFunction get tickFormat => _tickFormat;
/// Draw an axis on each non-null element in selection
- draw(Selection g, {SvgAxisTicks axisTicksBuilder, bool isRTL: false}) =>
+ void draw(Selection g, {SvgAxisTicks axisTicksBuilder, bool isRTL: false}) =>
g.each((d, i, e) =>
create(e, g.scope, axisTicksBuilder: axisTicksBuilder, isRTL: isRTL));
/// Create an axis on [element].
- create(Element element, SelectionScope scope,
+ void create(Element element, SelectionScope scope,
{SvgAxisTicks axisTicksBuilder, bool isRTL: false}) {
var group = scope.selectElements([element]),
older = _scales[element],
@@ -83,21 +82,22 @@
isTop = !(isVertical || isBottom) && orientation == ORIENTATION_TOP,
isHorizontal = !isVertical;
- if (older == null) older = current;
- if (axisTicksBuilder == null) {
- axisTicksBuilder = new SvgAxisTicks();
- }
+ older ??= current;
+ axisTicksBuilder ??= new SvgAxisTicks();
axisTicksBuilder.init(this);
var values = axisTicksBuilder.ticks,
formatted = axisTicksBuilder.formattedTicks,
ellipsized = axisTicksBuilder.shortenedTicks;
- var ticks = group.selectAll('.tick').data(values, current.scale),
- exit = ticks.exit,
- transform = isVertical ? _yAxisTransform : _xAxisTransform,
- sign = isTop || isLeft ? -1 : 1,
- isEllipsized = ellipsized != formatted;
+ // Need to wrap the function `current.scale` to avoid fuzzy arrow.
+ dynamic Function(dynamic) fn = (value) => current.scale(value);
+
+ var ticks = group.selectAll('.tick').data(values, fn);
+ var exit = ticks.exit;
+ var transform = isVertical ? _yAxisTransform : _xAxisTransform;
+ var sign = isTop || isLeft ? -1 : 1;
+ var isEllipsized = ellipsized != formatted;
var enter = ticks.enter.appendWithCallback((d, i, e) {
var group = Namespace.createChildElement('g', e)
diff --git a/packages/charted/lib/svg/shapes/arc.dart b/packages/charted/lib/svg/shapes/arc.dart
index dfbcb70..a842449 100644
--- a/packages/charted/lib/svg/shapes/arc.dart
+++ b/packages/charted/lib/svg/shapes/arc.dart
@@ -91,12 +91,14 @@
/// Default [innerRadiusCallback] returns data.innerRadius
static num defaultInnerRadiusCallback(d, i, e) =>
d is! SvgArcData || d.innerRadius == null
- ? 0 : (d as SvgArcData).innerRadius;
+ ? 0
+ : (d as SvgArcData).innerRadius;
/// Default [outerRadiusCallback] returns data.outerRadius
static num defaultOuterRadiusCallback(d, i, e) =>
d is! SvgArcData || d.outerRadius == null
- ? 0 : (d as SvgArcData).outerRadius;
+ ? 0
+ : (d as SvgArcData).outerRadius;
/// Default [startAngleCallback] returns data.startAngle
static num defaultStartAngleCallback(d, i, e) =>
@@ -106,8 +108,7 @@
/// Default [endAngleCallback] that returns data.endAngle
static num defaultEndAngleCallback(d, i, e) =>
- d is! SvgArcData || d.endAngle == null
- ? 0 : (d as SvgArcData).endAngle;
+ d is! SvgArcData || d.endAngle == null ? 0 : (d as SvgArcData).endAngle;
}
/// Value type for SvgArc as used by default property accessors in SvgArc
diff --git a/packages/charted/lib/svg/shapes/line.dart b/packages/charted/lib/svg/shapes/line.dart
index f53bf0a..15fd1fe 100644
--- a/packages/charted/lib/svg/shapes/line.dart
+++ b/packages/charted/lib/svg/shapes/line.dart
@@ -75,8 +75,8 @@
/// Default implementation of [yValueAccessor].
/// Returns the second element if [d] is an iterable, otherwise returns [d].
- static num defaultDataToY(d, i) => d is Iterable
- ? d.elementAt(1) as num : d as num;
+ static num defaultDataToY(d, i) =>
+ d is Iterable ? d.elementAt(1) as num : d as num;
/// Default implementation of [isDefined].
/// Returns true for all non-null values of [d].
diff --git a/packages/charted/pubspec.yaml b/packages/charted/pubspec.yaml
index e92537a..0004e9e 100644
--- a/packages/charted/pubspec.yaml
+++ b/packages/charted/pubspec.yaml
@@ -1,5 +1,5 @@
name: charted
-version: 0.5.0
+version: 0.6.1
authors:
- Prasad Sunkari <prsd@google.com>
- Michael Cheng <midoringo@google.com>
@@ -7,12 +7,13 @@
description: Visualization toolkit for Dart - Provides D3 (http://d3js.org) like Selection API, utilities to achieve data driven DOM and an easy-to-use Charting library based on the Selection API.
homepage: https://github.com/google/charted
environment:
- sdk: ">=1.23.0 <2.0.0"
+ sdk: '>=2.0.0-dev.55.0 <3.0.0'
dependencies:
browser: any
+ collection: ^1.14.10
intl: any
logging: any
- observable: '>=0.14.0 <0.15.0'
+ observable: '^0.22.1'
quiver: any
dev_dependencies:
hop: '>=0.27.0'
diff --git a/packages/collection/.gitignore b/packages/collection/.gitignore
index 25a1df3..98d6d21 100644
--- a/packages/collection/.gitignore
+++ b/packages/collection/.gitignore
@@ -2,6 +2,7 @@
.DS_Store
.idea
.pub/
+.dart_tool/
.settings/
build/
packages
diff --git a/packages/collection/.travis.yml b/packages/collection/.travis.yml
index 264316d..b4d1316 100644
--- a/packages/collection/.travis.yml
+++ b/packages/collection/.travis.yml
@@ -1,24 +1,54 @@
+
language: dart
-dart:
- - dev
- - stable
+# Gives more resources on Travis (8GB Ram, 2 CPUs).
+# Do not remove without verifying w/ Travis.
+sudo: required
+addons:
+ chrome: stable
-dart_task:
- - test: --platform vm
- - test: --platform firefox -j 1
- - dartanalyzer
- - dartfmt
+# Build stages: https://docs.travis-ci.com/user/build-stages/.
+stages:
+ - presubmit
+ - build
+ - testing
+# 1. Run dartfmt, dartanalyzer, pub run test (VM).
+# 2. Then run a build.
+# 3. Then run tests compiled via dartdevc and dart2js.
+jobs:
+ include:
+ - stage: presubmit
+ script: ./tool/travis.sh dartfmt
+ dart: dev
+ - stage: presubmit
+ script: ./tool/travis.sh dartanalyzer
+ dart: dev
+ - stage: build
+ script: ./tool/travis.sh dartdevc_build
+ dart: dev
+ - stage: testing
+ script: ./tool/travis.sh vm_test
+ dart: dev
+ - stage: testing
+ script: ./tool/travis.sh dartdevc_test
+ dart: dev
+ - stage: testing
+ script: ./tool/travis.sh dart2js_test
+ dart: dev
+
+# Only building master means that we don't run two builds for each pull request.
branches:
only: [master]
-matrix:
- # Only run dartfmt checks with stable.
- exclude:
- - dart: dev
- dart_task: dartfmt
-
+# Incremental pub cache and builds.
cache:
directories:
- $HOME/.pub-cache
+ - .dart_tool
+
+# Necessary for Chrome and Firefox to run
+before_install:
+ - export DISPLAY=:99.0
+ - sh -e /etc/init.d/xvfb start
+ - "t=0; until (xdpyinfo -display :99 &> /dev/null || test $t -gt 10); do sleep 1; let t=$t+1; done"
diff --git a/packages/collection/CHANGELOG.md b/packages/collection/CHANGELOG.md
index b676ee1..3c9d4dd 100644
--- a/packages/collection/CHANGELOG.md
+++ b/packages/collection/CHANGELOG.md
@@ -1,3 +1,27 @@
+## 1.14.10
+
+* Fix the parameter names in overridden methods to match the source.
+* Make tests Dart 2 type-safe.
+* Stop depending on SDK `retype` and deprecate methods.
+
+## 1.14.9
+
+* Fixed bugs where `QueueList`, `MapKeySet`, and `MapValueSet` did not adhere to
+ the contract laid out by `List.cast`, `Set.cast` and `Map.cast` respectively.
+ The returned instances of these methods now correctly forward to the existing
+ instance instead of always creating a new copy.
+
+## 1.14.8
+
+* Deprecated `Delegating{Name}.typed` static methods in favor of the new Dart 2
+ `cast` methods. For example, `DelegatingList.typed<String>(list)` can now be
+ written as `list.cast<String>()`.
+
+## 1.14.7
+
+* Only the Dart 2 dev SDK (`>=2.0.0-dev.22.0`) is now supported.
+* Added support for all Dart 2 SDK methods that threw `UnimplementedError`.
+
## 1.14.6
* Make `DefaultEquality`'s `equals()` and `hash()` methods take any `Object`
@@ -15,7 +39,7 @@
## 1.14.4
* Add implementation stubs of upcoming Dart 2.0 core library methods, namely
- new methods for classes that implement `Iterable`, `List`, `Map`, `Queue`,
+ new methods for classes that implement `Iterable`, `List`, `Map`, `Queue`,
and `Set`.
## 1.14.3
diff --git a/packages/collection/CONTRIBUTING.md b/packages/collection/CONTRIBUTING.md
index 6f5e0ea..e88abc6 100644
--- a/packages/collection/CONTRIBUTING.md
+++ b/packages/collection/CONTRIBUTING.md
@@ -20,10 +20,19 @@
### Code reviews
All submissions, including submissions by project members, require review.
+### Presubmit testing
+* All code must pass analysis by the `dartanalyzer` (`dartanalyzer --fatal-warnings .`)
+* All code must be formatted by `dartfmt` (`dartfmt -w .`)
+ * _NOTE_: We currently require formatting by the `dev` channel SDK.
+* All code must pass unit tests for the VM, Dart2JS, and DartDevC (`pub run build_runner test`).
+ * _NOTE_: We currently use `build_runner` for compilation with DartDevC. It's
+ possible to run only Dart2JS and the VM without it using `pub run test`
+ directly.
+
### File headers
All files in the project must start with the following header.
- // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+ // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
diff --git a/packages/collection/analysis_options.yaml b/packages/collection/analysis_options.yaml
index 5b5eb96..705910c 100644
--- a/packages/collection/analysis_options.yaml
+++ b/packages/collection/analysis_options.yaml
@@ -20,6 +20,7 @@
# Style
- avoid_init_to_null
+ - avoid_renaming_method_parameters
- avoid_return_types_on_setters
- await_only_futures
- camel_case_types
@@ -32,6 +33,5 @@
- only_throw_errors
- prefer_final_fields
- prefer_is_not_empty
- - prefer_single_quotes
- slash_for_doc_comments
- type_init_formals
diff --git a/packages/collection/dart_test.yaml b/packages/collection/dart_test.yaml
new file mode 100644
index 0000000..d2b9d1d
--- /dev/null
+++ b/packages/collection/dart_test.yaml
@@ -0,0 +1,22 @@
+presets:
+ # When run with -P travis, we have different settings/options.
+ #
+ # 1: We don't use Chrome --headless:
+ # 2: We use --reporter expanded
+ # 3: We skip anything tagged "fails-on-travis".
+ travis:
+ # TODO(https://github.com/dart-lang/test/issues/772)
+ override_platforms:
+ chrome:
+ settings:
+ headless: false
+
+ # Don't run any tests that are tagged ["fails-on-travis"].
+ exclude_tags: "fails-on-travis"
+
+ # https://github.com/dart-lang/test/blob/master/doc/configuration.md#reporter
+ reporter: expanded
+
+platforms:
+ - chrome
+ - vm
diff --git a/packages/collection/lib/src/algorithms.dart b/packages/collection/lib/src/algorithms.dart
index 567230c..0d37d64 100644
--- a/packages/collection/lib/src/algorithms.dart
+++ b/packages/collection/lib/src/algorithms.dart
@@ -64,7 +64,7 @@
/// Shuffles a list randomly.
///
/// A sub-range of a list can be shuffled by providing [start] and [end].
-void shuffle(List list, [int start = 0, int end = null]) {
+void shuffle(List list, [int start = 0, int end]) {
var random = new math.Random();
if (end == null) end = list.length;
int length = end - start;
@@ -78,7 +78,7 @@
}
/// Reverses a list, or a part of a list, in-place.
-void reverse(List list, [int start = 0, int end = null]) {
+void reverse(List list, [int start = 0, int end]) {
if (end == null) end = list.length;
_reverse(list, start, end);
}
diff --git a/packages/collection/lib/src/canonicalized_map.dart b/packages/collection/lib/src/canonicalized_map.dart
index 117cd63..9e1d5cf 100644
--- a/packages/collection/lib/src/canonicalized_map.dart
+++ b/packages/collection/lib/src/canonicalized_map.dart
@@ -69,17 +69,11 @@
other.forEach((key, value) => this[key] = value);
}
- // TODO: Dart 2.0 requires this method to be implemented.
- void addEntries(Iterable<Object> entries) {
- // Change Iterable<Object> to Iterable<MapEntry<K, V>> when
- // the MapEntry class has been added.
- throw new UnimplementedError('addEntries');
- }
+ void addEntries(Iterable<MapEntry<K, V>> entries) =>
+ _base.addEntries(entries.map(
+ (e) => new MapEntry(_canonicalize(e.key), new Pair(e.key, e.value))));
- // TODO: Dart 2.0 requires this method to be implemented.
- Map<K2, V2> cast<K2, V2>() {
- throw new UnimplementedError('cast');
- }
+ Map<K2, V2> cast<K2, V2>() => _base.cast<K2, V2>();
void clear() {
_base.clear();
@@ -93,12 +87,8 @@
bool containsValue(Object value) =>
_base.values.any((pair) => pair.last == value);
- // TODO: Dart 2.0 requires this method to be implemented.
- Iterable<Null> get entries {
- // Change Iterable<Null> to Iterable<MapEntry<K, V>> when
- // the MapEntry class has been added.
- throw new UnimplementedError('entries');
- }
+ Iterable<MapEntry<K, V>> get entries =>
+ _base.entries.map((e) => new MapEntry(e.value.first, e.value.last));
void forEach(void f(K key, V value)) {
_base.forEach((key, pair) => f(pair.first, pair.last));
@@ -112,12 +102,8 @@
int get length => _base.length;
- // TODO: Dart 2.0 requires this method to be implemented.
- Map<K2, V2> map<K2, V2>(Object transform(K key, V value)) {
- // Change Object to MapEntry<K2, V2> when
- // the MapEntry class has been added.
- throw new UnimplementedError('map');
- }
+ Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> transform(K key, V value)) =>
+ _base.map((_, pair) => transform(pair.first, pair.last));
V putIfAbsent(K key, V ifAbsent()) {
return _base
@@ -131,25 +117,19 @@
return pair == null ? null : pair.last;
}
- // TODO: Dart 2.0 requires this method to be implemented.
- void removeWhere(bool test(K key, V value)) {
- throw new UnimplementedError('removeWhere');
- }
+ void removeWhere(bool test(K key, V value)) =>
+ _base.removeWhere((_, pair) => test(pair.first, pair.last));
- // TODO: Dart 2.0 requires this method to be implemented.
- Map<K2, V2> retype<K2, V2>() {
- throw new UnimplementedError('retype');
- }
+ @deprecated
+ Map<K2, V2> retype<K2, V2>() => cast<K2, V2>();
- // TODO: Dart 2.0 requires this method to be implemented.
- V update(K key, V update(V value), {V ifAbsent()}) {
- throw new UnimplementedError('update');
- }
+ V update(K key, V update(V value), {V ifAbsent()}) => _base
+ .update(_canonicalize(key), (pair) => new Pair(key, update(pair.last)),
+ ifAbsent: ifAbsent == null ? null : () => new Pair(key, ifAbsent()))
+ .last;
- // TODO: Dart 2.0 requires this method to be implemented.
- void updateAll(V update(K key, V value)) {
- throw new UnimplementedError('updateAll');
- }
+ void updateAll(V update(K key, V value)) => _base.updateAll(
+ (_, pair) => new Pair(pair.first, update(pair.first, pair.last)));
Iterable<V> get values => _base.values.map((pair) => pair.last);
diff --git a/packages/collection/lib/src/combined_wrappers/combined_list.dart b/packages/collection/lib/src/combined_wrappers/combined_list.dart
index d2e0349..21a68f6 100644
--- a/packages/collection/lib/src/combined_wrappers/combined_list.dart
+++ b/packages/collection/lib/src/combined_wrappers/combined_list.dart
@@ -56,11 +56,11 @@
return null;
}
- void removeWhere(bool filter(T element)) {
+ void removeWhere(bool test(T element)) {
_throw();
}
- void retainWhere(bool filter(T element)) {
+ void retainWhere(bool test(T element)) {
_throw();
}
}
diff --git a/packages/collection/lib/src/empty_unmodifiable_set.dart b/packages/collection/lib/src/empty_unmodifiable_set.dart
index 436e88d..997619d 100644
--- a/packages/collection/lib/src/empty_unmodifiable_set.dart
+++ b/packages/collection/lib/src/empty_unmodifiable_set.dart
@@ -25,6 +25,7 @@
bool containsAll(Iterable<Object> other) => other.isEmpty;
Iterable<E> followedBy(Iterable<E> other) => new Set.from(other);
E lookup(Object element) => null;
+ @deprecated
EmptyUnmodifiableSet<T> retype<T>() => new EmptyUnmodifiableSet<T>();
E singleWhere(bool test(E element), {E orElse()}) => super.singleWhere(test);
Iterable<T> whereType<T>() => new EmptyUnmodifiableSet<T>();
diff --git a/packages/collection/lib/src/queue_list.dart b/packages/collection/lib/src/queue_list.dart
index 224ac86..8b706bf 100644
--- a/packages/collection/lib/src/queue_list.dart
+++ b/packages/collection/lib/src/queue_list.dart
@@ -10,6 +10,21 @@
// that are redundant with ListMixin. Remove or simplify it when issue 21330 is
// fixed.
class QueueList<E> extends Object with ListMixin<E> implements Queue<E> {
+ /// Adapts [source] to be a `QueueList<T>`.
+ ///
+ /// Any time the class would produce an element that is not a [T], the element
+ /// access will throw.
+ ///
+ /// Any time a [T] value is attempted stored into the adapted class, the store
+ /// will throw unless the value is also an instance of [S].
+ ///
+ /// If all accessed elements of [source] are actually instances of [T] and if
+ /// all elements stored in the returned are actually instance of [S],
+ /// then the returned instance can be used as a `QueueList<T>`.
+ static QueueList<T> _castFrom<S, T>(QueueList<S> source) {
+ return new _CastQueueList<S, T>(source);
+ }
+
static const int _INITIAL_CAPACITY = 8;
List<E> _table;
int _head;
@@ -31,6 +46,9 @@
_table = new List<E>(initialCapacity);
}
+ // An internal constructor for use by _CastQueueList.
+ QueueList._();
+
/// Create a queue initially containing the elements of [source].
factory QueueList.from(Iterable<E> source) {
if (source is List) {
@@ -52,9 +70,9 @@
_add(element);
}
- void addAll(Iterable<E> elements) {
- if (elements is List) {
- var list = elements;
+ void addAll(Iterable<E> iterable) {
+ if (iterable is List) {
+ var list = iterable;
int addCount = list.length;
int length = this.length;
if (length + addCount >= _table.length) {
@@ -76,19 +94,14 @@
}
}
} else {
- for (E element in elements) _add(element);
+ for (E element in iterable) _add(element);
}
}
- // TODO: Dart 2.0 requires this method to be implemented.
- QueueList<T> cast<T>() {
- throw new UnimplementedError('cast');
- }
+ QueueList<T> cast<T>() => QueueList._castFrom<E, T>(this);
- // TODO: Dart 2.0 requires this method to be implemented.
- QueueList<T> retype<T>() {
- throw new UnimplementedError('retype');
- }
+ @deprecated
+ QueueList<T> retype<T>() => cast<T>();
String toString() => IterableBase.iterableToFullString(this, "{", "}");
@@ -231,3 +244,17 @@
_head = 0;
}
}
+
+class _CastQueueList<S, T> extends QueueList<T> {
+ final QueueList<S> _delegate;
+
+ _CastQueueList(this._delegate) : super._() {
+ _table = _delegate._table.cast<T>();
+ }
+
+ int get _head => _delegate._head;
+ set _head(int value) => _delegate._head = value;
+
+ int get _tail => _delegate._tail;
+ set _tail(int value) => _delegate._tail = value;
+}
diff --git a/packages/collection/lib/src/typed_wrappers.dart b/packages/collection/lib/src/typed_wrappers.dart
deleted file mode 100644
index cef2a58..0000000
--- a/packages/collection/lib/src/typed_wrappers.dart
+++ /dev/null
@@ -1,471 +0,0 @@
-// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import "dart:collection";
-import "dart:math" as math;
-
-import "wrappers.dart";
-
-typedef F _UnaryFunction<E, F>(E argument);
-
-/// The base class for delegating, type-asserting iterables.
-///
-/// Subclasses can provide a [_base] that should be delegated to. Unlike
-/// [TypeSafeIterable], this allows the base to be created on demand.
-abstract class _TypeSafeIterableBase<E> implements Iterable<E> {
- /// The base iterable to which operations are delegated.
- Iterable get _base;
-
- _TypeSafeIterableBase();
-
- bool any(bool test(E element)) => _base.any(_validate(test));
-
- // TODO: Dart 2.0 requires this method to be implemented.
- Iterable<T> cast<T>() {
- throw new UnimplementedError('cast');
- }
-
- bool contains(Object element) => _base.contains(element);
-
- E elementAt(int index) => _base.elementAt(index) as E;
-
- bool every(bool test(E element)) => _base.every(_validate(test));
-
- Iterable<T> expand<T>(Iterable<T> f(E element)) => _base.expand(_validate(f));
-
- E get first => _base.first as E;
-
- E firstWhere(bool test(E element), {E orElse()}) =>
- _base.firstWhere(_validate(test), orElse: orElse) as E;
-
- T fold<T>(T initialValue, T combine(T previousValue, E element)) =>
- _base.fold(initialValue,
- (previousValue, element) => combine(previousValue, element as E));
-
- // TODO: Dart 2.0 requires this method to be implemented.
- Iterable<E> followedBy(Iterable<E> other) {
- throw new UnimplementedError('followedBy');
- }
-
- void forEach(void f(E element)) => _base.forEach(_validate(f));
-
- bool get isEmpty => _base.isEmpty;
-
- bool get isNotEmpty => _base.isNotEmpty;
-
- Iterator<E> get iterator => _base.map((element) => element as E).iterator;
-
- String join([String separator = ""]) => _base.join(separator);
-
- E get last => _base.last as E;
-
- E lastWhere(bool test(E element), {E orElse()}) =>
- _base.lastWhere(_validate(test), orElse: orElse) as E;
-
- int get length => _base.length;
-
- Iterable<T> map<T>(T f(E element)) => _base.map(_validate(f));
-
- E reduce(E combine(E value, E element)) =>
- _base.reduce((value, element) => combine(value as E, element as E)) as E;
-
- // TODO: Dart 2.0 requires this method to be implemented.
- Iterable<T> retype<T>() {
- throw new UnimplementedError('retype');
- }
-
- E get single => _base.single as E;
-
- E singleWhere(bool test(E element), {E orElse()}) {
- if (orElse != null) throw new UnimplementedError('singleWhere:orElse');
- return _base.singleWhere(_validate(test)) as E;
- }
-
- Iterable<E> skip(int n) => new TypeSafeIterable<E>(_base.skip(n));
-
- Iterable<E> skipWhile(bool test(E value)) =>
- new TypeSafeIterable<E>(_base.skipWhile(_validate(test)));
-
- Iterable<E> take(int n) => new TypeSafeIterable<E>(_base.take(n));
-
- Iterable<E> takeWhile(bool test(E value)) =>
- new TypeSafeIterable<E>(_base.takeWhile(_validate(test)));
-
- List<E> toList({bool growable: true}) =>
- new TypeSafeList<E>(_base.toList(growable: growable));
-
- Set<E> toSet() => new TypeSafeSet<E>(_base.toSet());
-
- Iterable<E> where(bool test(E element)) =>
- new TypeSafeIterable<E>(_base.where(_validate(test)));
-
- // TODO: Dart 2.0 requires this method to be implemented.
- Iterable<T> whereType<T>() {
- throw new UnimplementedError('whereType');
- }
-
- String toString() => _base.toString();
-
- /// Returns a version of [function] that asserts that its argument is an
- /// instance of `E`.
- _UnaryFunction<dynamic, F> _validate<F>(F function(E value)) =>
- (value) => function(value as E);
-}
-
-/// An [Iterable] that asserts the types of values in a base iterable.
-///
-/// This is instantiated using [DelegatingIterable.typed].
-class TypeSafeIterable<E> extends _TypeSafeIterableBase<E>
- implements DelegatingIterable<E> {
- final Iterable _base;
-
- TypeSafeIterable(Iterable base) : _base = base;
-}
-
-/// A [List] that asserts the types of values in a base list.
-///
-/// This is instantiated using [DelegatingList.typed].
-class TypeSafeList<E> extends TypeSafeIterable<E> implements DelegatingList<E> {
- TypeSafeList(List base) : super(base);
-
- /// A [List]-typed getter for [_base].
- List get _listBase => _base;
-
- E operator [](int index) => _listBase[index] as E;
-
- void operator []=(int index, E value) {
- _listBase[index] = value;
- }
-
- // TODO: Dart 2.0 requires this method to be implemented.
- List<E> operator +(List<E> other) {
- throw new UnimplementedError('+');
- }
-
- void add(E value) {
- _listBase.add(value);
- }
-
- void addAll(Iterable<E> iterable) {
- _listBase.addAll(iterable);
- }
-
- Map<int, E> asMap() => new TypeSafeMap<int, E>(_listBase.asMap());
-
- // TODO: Dart 2.0 requires this method to be implemented.
- List<T> cast<T>() {
- throw new UnimplementedError('cast');
- }
-
- void clear() {
- _listBase.clear();
- }
-
- void fillRange(int start, int end, [E fillValue]) {
- _listBase.fillRange(start, end, fillValue);
- }
-
- // TODO: Dart 2.0 requires this method to be implemented.
- set first(E value) {
- if (this.isEmpty) throw new RangeError.index(0, this);
- this[0] = value;
- }
-
- Iterable<E> getRange(int start, int end) =>
- new TypeSafeIterable<E>(_listBase.getRange(start, end));
-
- int indexOf(E element, [int start = 0]) => _listBase.indexOf(element, start);
-
- // TODO: Dart 2.0 requires this method to be implemented.
- int indexWhere(bool test(E element), [int start = 0]) {
- throw new UnimplementedError('indexWhere');
- }
-
- void insert(int index, E element) {
- _listBase.insert(index, element);
- }
-
- void insertAll(int index, Iterable<E> iterable) {
- _listBase.insertAll(index, iterable);
- }
-
- // TODO: Dart 2.0 requires this method to be implemented.
- set last(E value) {
- if (this.isEmpty) throw new RangeError.index(0, this);
- this[this.length - 1] = value;
- }
-
- int lastIndexOf(E element, [int start]) =>
- _listBase.lastIndexOf(element, start);
-
- // TODO: Dart 2.0 requires this method to be implemented.
- int lastIndexWhere(bool test(E element), [int start]) {
- throw new UnimplementedError('lastIndexWhere');
- }
-
- set length(int newLength) {
- _listBase.length = newLength;
- }
-
- bool remove(Object value) => _listBase.remove(value);
-
- E removeAt(int index) => _listBase.removeAt(index) as E;
-
- E removeLast() => _listBase.removeLast() as E;
-
- void removeRange(int start, int end) {
- _listBase.removeRange(start, end);
- }
-
- void removeWhere(bool test(E element)) {
- _listBase.removeWhere(_validate(test));
- }
-
- void replaceRange(int start, int end, Iterable<E> iterable) {
- _listBase.replaceRange(start, end, iterable);
- }
-
- void retainWhere(bool test(E element)) {
- _listBase.retainWhere(_validate(test));
- }
-
- // TODO: Dart 2.0 requires this method to be implemented.
- List<T> retype<T>() {
- throw new UnimplementedError('retype');
- }
-
- Iterable<E> get reversed => new TypeSafeIterable<E>(_listBase.reversed);
-
- void setAll(int index, Iterable<E> iterable) {
- _listBase.setAll(index, iterable);
- }
-
- void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
- _listBase.setRange(start, end, iterable, skipCount);
- }
-
- void shuffle([math.Random random]) {
- _listBase.shuffle(random);
- }
-
- void sort([int compare(E a, E b)]) {
- if (compare == null) {
- _listBase.sort();
- } else {
- _listBase.sort((a, b) => compare(a as E, b as E));
- }
- }
-
- List<E> sublist(int start, [int end]) =>
- new TypeSafeList<E>(_listBase.sublist(start, end));
-}
-
-/// A [Set] that asserts the types of values in a base set.
-///
-/// This is instantiated using [DelegatingSet.typed].
-class TypeSafeSet<E> extends TypeSafeIterable<E> implements DelegatingSet<E> {
- TypeSafeSet(Set base) : super(base);
-
- /// A [Set]-typed getter for [_base].
- Set get _setBase => _base;
-
- bool add(E value) => _setBase.add(value);
-
- void addAll(Iterable<E> elements) {
- _setBase.addAll(elements);
- }
-
- // TODO: Dart 2.0 requires this method to be implemented.
- Set<T> cast<T>() {
- throw new UnimplementedError('cast');
- }
-
- void clear() {
- _setBase.clear();
- }
-
- bool containsAll(Iterable<Object> other) => _setBase.containsAll(other);
-
- Set<E> difference(Set<Object> other) =>
- new TypeSafeSet<E>(_setBase.difference(other));
-
- Set<E> intersection(Set<Object> other) =>
- new TypeSafeSet<E>(_setBase.intersection(other));
-
- E lookup(Object element) => _setBase.lookup(element) as E;
-
- bool remove(Object value) => _setBase.remove(value);
-
- void removeAll(Iterable<Object> elements) {
- _setBase.removeAll(elements);
- }
-
- void removeWhere(bool test(E element)) {
- _setBase.removeWhere(_validate(test));
- }
-
- void retainAll(Iterable<Object> elements) {
- _setBase.retainAll(elements);
- }
-
- void retainWhere(bool test(E element)) {
- _setBase.retainWhere(_validate(test));
- }
-
- // TODO: Dart 2.0 requires this method to be implemented.
- Set<T> retype<T>() {
- throw new UnimplementedError('retype');
- }
-
- Set<E> union(Set<E> other) => new TypeSafeSet<E>(_setBase.union(other));
-}
-
-/// A [Queue] that asserts the types of values in a base queue.
-///
-/// This is instantiated using [DelegatingQueue.typed].
-class TypeSafeQueue<E> extends TypeSafeIterable<E>
- implements DelegatingQueue<E> {
- TypeSafeQueue(Queue queue) : super(queue);
-
- /// A [Queue]-typed getter for [_base].
- Queue get _baseQueue => _base;
-
- void add(E value) {
- _baseQueue.add(value);
- }
-
- void addAll(Iterable<E> iterable) {
- _baseQueue.addAll(iterable);
- }
-
- void addFirst(E value) {
- _baseQueue.addFirst(value);
- }
-
- void addLast(E value) {
- _baseQueue.addLast(value);
- }
-
- // TODO: Dart 2.0 requires this method to be implemented.
- Queue<T> cast<T>() {
- throw new UnimplementedError('cast');
- }
-
- void clear() {
- _baseQueue.clear();
- }
-
- bool remove(Object object) => _baseQueue.remove(object);
-
- void removeWhere(bool test(E element)) {
- _baseQueue.removeWhere(_validate(test));
- }
-
- void retainWhere(bool test(E element)) {
- _baseQueue.retainWhere(_validate(test));
- }
-
- // TODO: Dart 2.0 requires this method to be implemented.
- Queue<T> retype<T>() {
- throw new UnimplementedError('retype');
- }
-
- E removeFirst() => _baseQueue.removeFirst() as E;
-
- E removeLast() => _baseQueue.removeLast() as E;
-}
-
-/// A [Map] that asserts the types of keys and values in a base map.
-///
-/// This is instantiated using [DelegatingMap.typed].
-class TypeSafeMap<K, V> implements DelegatingMap<K, V> {
- /// The base map to which operations are delegated.
- final Map _base;
-
- TypeSafeMap(Map base) : _base = base;
-
- V operator [](Object key) => _base[key] as V;
-
- void operator []=(K key, V value) {
- _base[key] = value;
- }
-
- void addAll(Map<K, V> other) {
- _base.addAll(other);
- }
-
- // TODO: Dart 2.0 requires this method to be implemented.
- void addEntries(Iterable<Object> entries) {
- // Change Iterable<Object> to Iterable<MapEntry<K, V>> when
- // the MapEntry class has been added.
- throw new UnimplementedError('addEntries');
- }
-
- // TODO: Dart 2.0 requires this method to be implemented.
- Map<K2, V2> cast<K2, V2>() {
- throw new UnimplementedError('cast');
- }
-
- void clear() {
- _base.clear();
- }
-
- bool containsKey(Object key) => _base.containsKey(key);
-
- bool containsValue(Object value) => _base.containsValue(value);
-
- // TODO: Dart 2.0 requires this method to be implemented.
- Iterable<Null> get entries {
- // Change Iterable<Null> to Iterable<MapEntry<K, V>> when
- // the MapEntry class has been added.
- throw new UnimplementedError('entries');
- }
-
- void forEach(void f(K key, V value)) {
- _base.forEach((key, value) => f(key as K, value as V));
- }
-
- bool get isEmpty => _base.isEmpty;
-
- bool get isNotEmpty => _base.isNotEmpty;
-
- Iterable<K> get keys => new TypeSafeIterable<K>(_base.keys);
-
- int get length => _base.length;
-
- // TODO: Dart 2.0 requires this method to be implemented.
- Map<K2, V2> map<K2, V2>(Object transform(K key, V value)) {
- // Change Object to MapEntry<K2, V2> when
- // the MapEntry class has been added.
- throw new UnimplementedError('map');
- }
-
- V putIfAbsent(K key, V ifAbsent()) => _base.putIfAbsent(key, ifAbsent) as V;
-
- V remove(Object key) => _base.remove(key) as V;
-
- // TODO: Dart 2.0 requires this method to be implemented.
- void removeWhere(bool test(K key, V value)) {
- throw new UnimplementedError('removeWhere');
- }
-
- // TODO: Dart 2.0 requires this method to be implemented.
- Map<K2, V2> retype<K2, V2>() {
- throw new UnimplementedError('retype');
- }
-
- Iterable<V> get values => new TypeSafeIterable<V>(_base.values);
-
- String toString() => _base.toString();
-
- // TODO: Dart 2.0 requires this method to be implemented.
- V update(K key, V update(V value), {V ifAbsent()}) {
- throw new UnimplementedError('update');
- }
-
- // TODO: Dart 2.0 requires this method to be implemented.
- void updateAll(V update(K key, V value)) {
- throw new UnimplementedError('updateAll');
- }
-}
diff --git a/packages/collection/lib/src/wrappers.dart b/packages/collection/lib/src/wrappers.dart
index 1abfd45..f7fe578 100644
--- a/packages/collection/lib/src/wrappers.dart
+++ b/packages/collection/lib/src/wrappers.dart
@@ -5,7 +5,6 @@
import "dart:collection";
import "dart:math" as math;
-import "typed_wrappers.dart";
import "unmodifiable_wrappers.dart";
typedef K _KeyForValue<K, V>(V value);
@@ -21,10 +20,7 @@
bool any(bool test(E element)) => _base.any(test);
- // TODO: Dart 2.0 requires this method to be implemented.
- Iterable<T> cast<T>() {
- throw new UnimplementedError('cast');
- }
+ Iterable<T> cast<T>() => _base.cast<T>();
bool contains(Object element) => _base.contains(element);
@@ -42,10 +38,7 @@
T fold<T>(T initialValue, T combine(T previousValue, E element)) =>
_base.fold(initialValue, combine);
- // TODO: Dart 2.0 requires this method to be implemented.
- Iterable<E> followedBy(Iterable<E> other) {
- throw new UnimplementedError('followedBy');
- }
+ Iterable<E> followedBy(Iterable<E> other) => _base.followedBy(other);
void forEach(void f(E element)) => _base.forEach(f);
@@ -68,16 +61,13 @@
E reduce(E combine(E value, E element)) => _base.reduce(combine);
- // TODO: Dart 2.0 requires this method to be implemented.
- Iterable<T> retype<T>() {
- throw new UnimplementedError('retype');
- }
+ @deprecated
+ Iterable<T> retype<T>() => cast<T>();
E get single => _base.single;
E singleWhere(bool test(E element), {E orElse()}) {
- if (orElse != null) throw new UnimplementedError('singleWhere:orElse');
- return _base.singleWhere(test);
+ return _base.singleWhere(test, orElse: orElse);
}
Iterable<E> skip(int n) => _base.skip(n);
@@ -94,10 +84,7 @@
Iterable<E> where(bool test(E element)) => _base.where(test);
- // TODO: Dart 2.0 requires this method to be implemented.
- Iterable<T> whereType<T>() {
- throw new UnimplementedError("whereType");
- }
+ Iterable<T> whereType<T>() => _base.whereType<T>();
String toString() => _base.toString();
}
@@ -122,8 +109,8 @@
/// This forwards all operations to [base], so any changes in [base] will be
/// reflected in [this]. If [base] is already an `Iterable<E>`, it's returned
/// unmodified.
- static Iterable<E> typed<E>(Iterable base) =>
- base is Iterable<E> ? base : new TypeSafeIterable<E>(base);
+ @Deprecated('Use iterable.cast<E> instead.')
+ static Iterable<E> typed<E>(Iterable base) => base.cast<E>();
}
/// A [List] that delegates all operations to a base list.
@@ -145,8 +132,8 @@
/// This forwards all operations to [base], so any changes in [base] will be
/// reflected in [this]. If [base] is already a `List<E>`, it's returned
/// unmodified.
- static List<E> typed<E>(List base) =>
- base is List<E> ? base : new TypeSafeList<E>(base);
+ @Deprecated('Use list.cast<E> instead.')
+ static List<E> typed<E>(List base) => base.cast<E>();
List<E> get _listBase => _base;
@@ -156,10 +143,7 @@
_listBase[index] = value;
}
- // TODO: Dart 2.0 requires this method to be implemented.
- List<E> operator +(List<E> other) {
- throw new UnimplementedError('+');
- }
+ List<E> operator +(List<E> other) => _listBase + other;
void add(E value) {
_listBase.add(value);
@@ -171,10 +155,7 @@
Map<int, E> asMap() => _listBase.asMap();
- // TODO: Dart 2.0 requires this method to be implemented.
- List<T> cast<T>() {
- throw new UnimplementedError('cast');
- }
+ List<T> cast<T>() => _listBase.cast<T>();
void clear() {
_listBase.clear();
@@ -184,7 +165,6 @@
_listBase.fillRange(start, end, fillValue);
}
- // TODO: Dart 2.0 requires this method to be implemented.
set first(E value) {
if (this.isEmpty) throw new RangeError.index(0, this);
this[0] = value;
@@ -194,10 +174,8 @@
int indexOf(E element, [int start = 0]) => _listBase.indexOf(element, start);
- // TODO: Dart 2.0 requires this method to be implemented.
- int indexWhere(bool test(E element), [int start = 0]) {
- throw new UnimplementedError('indexWhere');
- }
+ int indexWhere(bool test(E element), [int start = 0]) =>
+ _listBase.indexWhere(test, start);
void insert(int index, E element) {
_listBase.insert(index, element);
@@ -207,7 +185,6 @@
_listBase.insertAll(index, iterable);
}
- // TODO: Dart 2.0 requires this method to be implemented.
set last(E value) {
if (this.isEmpty) throw new RangeError.index(0, this);
this[this.length - 1] = value;
@@ -216,10 +193,8 @@
int lastIndexOf(E element, [int start]) =>
_listBase.lastIndexOf(element, start);
- // TODO: Dart 2.0 requires this method to be implemented.
- int lastIndexWhere(bool test(E element), [int start]) {
- throw new UnimplementedError('lastIndexWhere');
- }
+ int lastIndexWhere(bool test(E element), [int start]) =>
+ _listBase.lastIndexWhere(test, start);
set length(int newLength) {
_listBase.length = newLength;
@@ -247,10 +222,8 @@
_listBase.retainWhere(test);
}
- // TODO: Dart 2.0 requires this method to be implemented.
- List<T> retype<T>() {
- throw new UnimplementedError('retype');
- }
+ @deprecated
+ List<T> retype<T>() => cast<T>();
Iterable<E> get reversed => _listBase.reversed;
@@ -291,8 +264,8 @@
/// This forwards all operations to [base], so any changes in [base] will be
/// reflected in [this]. If [base] is already a `Set<E>`, it's returned
/// unmodified.
- static Set<E> typed<E>(Set base) =>
- base is Set<E> ? base : new TypeSafeSet<E>(base);
+ @Deprecated('Use set.cast<E> instead.')
+ static Set<E> typed<E>(Set base) => base.cast<E>();
Set<E> get _setBase => _base;
@@ -302,10 +275,7 @@
_setBase.addAll(elements);
}
- // TODO: Dart 2.0 requires this method to be implemented.
- Set<T> cast<T>() {
- throw new UnimplementedError('cast');
- }
+ Set<T> cast<T>() => _setBase.cast<T>();
void clear() {
_setBase.clear();
@@ -333,10 +303,8 @@
_setBase.retainAll(elements);
}
- // TODO: Dart 2.0 requires this method to be implemented.
- Set<T> retype<T>() {
- throw new UnimplementedError('retype');
- }
+ @deprecated
+ Set<T> retype<T>() => cast<T>();
void retainWhere(bool test(E element)) {
_setBase.retainWhere(test);
@@ -366,8 +334,8 @@
/// This forwards all operations to [base], so any changes in [base] will be
/// reflected in [this]. If [base] is already a `Queue<E>`, it's returned
/// unmodified.
- static Queue<E> typed<E>(Queue base) =>
- base is Queue<E> ? base : new TypeSafeQueue<E>(base);
+ @Deprecated('Use queue.cast<E> instead.')
+ static Queue<E> typed<E>(Queue base) => base.cast<E>();
Queue<E> get _baseQueue => _base;
@@ -387,10 +355,7 @@
_baseQueue.addLast(value);
}
- // TODO: Dart 2.0 requires this method to be implemented.
- Queue<T> cast<T>() {
- throw new UnimplementedError('cast');
- }
+ Queue<T> cast<T>() => _baseQueue.cast<T>();
void clear() {
_baseQueue.clear();
@@ -406,10 +371,8 @@
_baseQueue.retainWhere(test);
}
- // TODO: Dart 2.0 requires this method to be implemented.
- Queue<T> retype<T>() {
- throw new UnimplementedError('retype');
- }
+ @deprecated
+ Queue<T> retype<T>() => cast<T>();
E removeFirst() => _baseQueue.removeFirst();
@@ -437,8 +400,8 @@
/// This forwards all operations to [base], so any changes in [base] will be
/// reflected in [this]. If [base] is already a `Map<K, V>`, it's returned
/// unmodified.
- static Map<K, V> typed<K, V>(Map base) =>
- base is Map<K, V> ? base : new TypeSafeMap<K, V>(base);
+ @Deprecated('Use map.cast<K, V> instead.')
+ static Map<K, V> typed<K, V>(Map base) => base.cast<K, V>();
V operator [](Object key) => _base[key];
@@ -450,32 +413,21 @@
_base.addAll(other);
}
- // TODO: Dart 2.0 requires this method to be implemented.
- void addEntries(Iterable<Object> entries) {
- // Change Iterable<Object> to Iterable<MapEntry<K, V>> when
- // the MapEntry class has been added.
- throw new UnimplementedError('addEntries');
+ void addEntries(Iterable<MapEntry<K, V>> entries) {
+ _base.addEntries(entries);
}
void clear() {
_base.clear();
}
- // TODO: Dart 2.0 requires this method to be implemented.
- Map<K2, V2> cast<K2, V2>() {
- throw new UnimplementedError('cast');
- }
+ Map<K2, V2> cast<K2, V2>() => _base.cast<K2, V2>();
bool containsKey(Object key) => _base.containsKey(key);
bool containsValue(Object value) => _base.containsValue(value);
- // TODO: Dart 2.0 requires this method to be implemented.
- Iterable<Null> get entries {
- // Change Iterable<Null> to Iterable<MapEntry<K, V>> when
- // the MapEntry class has been added.
- throw new UnimplementedError('entries');
- }
+ Iterable<MapEntry<K, V>> get entries => _base.entries;
void forEach(void f(K key, V value)) {
_base.forEach(f);
@@ -489,40 +441,26 @@
int get length => _base.length;
- // TODO: Dart 2.0 requires this method to be implemented.
- Map<K2, V2> map<K2, V2>(Object transform(K key, V value)) {
- // Change Object to MapEntry<K2, V2> when
- // the MapEntry class has been added.
- throw new UnimplementedError('map');
- }
+ Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> transform(K key, V value)) =>
+ _base.map(transform);
V putIfAbsent(K key, V ifAbsent()) => _base.putIfAbsent(key, ifAbsent);
V remove(Object key) => _base.remove(key);
- // TODO: Dart 2.0 requires this method to be implemented.
- void removeWhere(bool test(K key, V value)) {
- throw new UnimplementedError('removeWhere');
- }
+ void removeWhere(bool test(K key, V value)) => _base.removeWhere(test);
- // TODO: Dart 2.0 requires this method to be implemented.
- Map<K2, V2> retype<K2, V2>() {
- throw new UnimplementedError('retype');
- }
+ @deprecated
+ Map<K2, V2> retype<K2, V2>() => cast<K2, V2>();
Iterable<V> get values => _base.values;
String toString() => _base.toString();
- // TODO: Dart 2.0 requires this method to be implemented.
- V update(K key, V update(V value), {V ifAbsent()}) {
- throw new UnimplementedError('update');
- }
+ V update(K key, V update(V value), {V ifAbsent()}) =>
+ _base.update(key, update, ifAbsent: ifAbsent);
- // TODO: Dart 2.0 requires this method to be implemented.
- void updateAll(V update(K key, V value)) {
- throw new UnimplementedError('updateAll');
- }
+ void updateAll(V update(K key, V value)) => _base.updateAll(update);
}
/// An unmodifiable [Set] view of the keys of a [Map].
@@ -542,9 +480,11 @@
Iterable<E> get _base => _baseMap.keys;
- // TODO: Dart 2.0 requires this method to be implemented.
Set<T> cast<T>() {
- throw new UnimplementedError('cast');
+ if (this is MapKeySet<T>) {
+ return this as MapKeySet<T>;
+ }
+ return Set.castFrom<E, T>(this);
}
bool contains(Object element) => _baseMap.containsKey(element);
@@ -583,10 +523,8 @@
E lookup(Object element) =>
throw new UnsupportedError("MapKeySet doesn't support lookup().");
- // TODO: Dart 2.0 requires this method to be implemented.
- Set<T> retype<T>() {
- throw new UnimplementedError('retype');
- }
+ @deprecated
+ Set<T> retype<T>() => Set.castFrom<E, T>(this);
/// Returns a new set which contains all the elements of [this] and [other].
///
@@ -634,9 +572,11 @@
Iterable<V> get _base => _baseMap.values;
- // TODO: Dart 2.0 requires this method to be implemented.
Set<T> cast<T>() {
- throw new UnimplementedError('cast');
+ if (this is Set<T>) {
+ return this as Set<T>;
+ }
+ return Set.castFrom<V, T>(this);
}
bool contains(Object element) {
@@ -735,10 +675,8 @@
void retainWhere(bool test(V element)) =>
removeWhere((element) => !test(element));
- // TODO: Dart 2.0 requires this method to be implemented.
- Set<T> retype<T>() {
- throw new UnimplementedError('retype');
- }
+ @deprecated
+ Set<T> retype<T>() => Set.castFrom<V, T>(this);
/// Returns a new set which contains all the elements of [this] and [other].
///
diff --git a/packages/collection/pubspec.yaml b/packages/collection/pubspec.yaml
index e97a358..323f039 100644
--- a/packages/collection/pubspec.yaml
+++ b/packages/collection/pubspec.yaml
@@ -1,9 +1,15 @@
name: collection
-version: 1.14.6
+version: 1.14.10
author: Dart Team <misc@dartlang.org>
description: Collections and utilities functions and classes related to collections.
homepage: https://www.github.com/dart-lang/collection
+
environment:
- sdk: '>=1.21.0 <2.0.0'
+ # Required for Dart 2.0 collection changes.
+ sdk: '>=2.0.0-dev.55.0 <2.0.0'
+
dev_dependencies:
- test: '^0.12.0'
+ build_runner: ^0.8.0
+ build_test: ^0.10.0
+ build_web_compilers: ^0.3.1
+ test: ^0.12.35
diff --git a/packages/collection/test/canonicalized_map_test.dart b/packages/collection/test/canonicalized_map_test.dart
index f48778d..078e57c 100644
--- a/packages/collection/test/canonicalized_map_test.dart
+++ b/packages/collection/test/canonicalized_map_test.dart
@@ -7,9 +7,10 @@
void main() {
group("with an empty canonicalized map", () {
- var map;
+ CanonicalizedMap<int, String, String> map;
+
setUp(() {
- map = new CanonicalizedMap<int, String, String>(int.parse,
+ map = new CanonicalizedMap(int.parse,
isValidKey: (s) => new RegExp(r"^\d+$").hasMatch(s as String));
});
@@ -130,6 +131,33 @@
expect(map.values, equals(["value 01", "value 2", "value 03"]));
});
+
+ test("entries returns all key-value pairs in the map", () {
+ map.addAll({
+ "1": "value 1",
+ "01": "value 01",
+ "2": "value 2",
+ });
+
+ var entries = map.entries.toList();
+ expect(entries[0].key, "01");
+ expect(entries[0].value, "value 01");
+ expect(entries[1].key, "2");
+ expect(entries[1].value, "value 2");
+ });
+
+ test("addEntries adds key-value pairs to the map", () {
+ map.addEntries([
+ new MapEntry("1", "value 1"),
+ new MapEntry("01", "value 01"),
+ new MapEntry("2", "value 2"),
+ ]);
+ expect(map, {"01": "value 01", "2": "value 2"});
+ });
+
+ test("cast returns a new map instance", () {
+ expect(map.cast<Pattern, Pattern>(), isNot(same(map)));
+ });
});
group("CanonicalizedMap builds an informative string representation", () {
diff --git a/packages/collection/test/queue_list_test.dart b/packages/collection/test/queue_list_test.dart
index 9e6d199..639eb68 100644
--- a/packages/collection/test/queue_list_test.dart
+++ b/packages/collection/test/queue_list_test.dart
@@ -5,6 +5,8 @@
import "package:collection/collection.dart";
import "package:test/test.dart";
+import "utils.dart";
+
void main() {
group("new QueueList()", () {
test("creates an empty QueueList", () {
@@ -250,6 +252,47 @@
throwsConcurrentModificationError);
});
});
+
+ test("cast does not throw on mutation when the type is valid", () {
+ var patternQueue = new QueueList<Pattern>()..addAll(['a', 'b']);
+ var stringQueue = patternQueue.cast<String>();
+ stringQueue.addAll(['c', 'd']);
+ expect(
+ stringQueue,
+ const isInstanceOf<QueueList<String>>(),
+ reason: 'Expected QueueList<String>, got ${stringQueue.runtimeType}',
+ skip: isDart2 ? false : 'Cast does nothing in Dart1',
+ );
+
+ expect(
+ stringQueue,
+ ['a', 'b', 'c', 'd'],
+ skip: isDart2 ? false : 'Cast does nothing in Dart1',
+ );
+
+ expect(patternQueue, stringQueue, reason: 'Should forward to original');
+ });
+
+ test("cast throws on mutation when the type is not valid", () {
+ QueueList<Object> stringQueue = new QueueList<String>();
+ var numQueue = stringQueue.cast<num>();
+ expect(
+ numQueue,
+ const isInstanceOf<QueueList<num>>(),
+ reason: 'Expected QueueList<num>, got ${numQueue.runtimeType}',
+ skip: isDart2 ? false : 'Cast does nothing in Dart1',
+ );
+ expect(
+ () => numQueue.add(1),
+ throwsCastError,
+ skip: isDart2 ? false : 'In Dart1 a TypeError is not thrown',
+ );
+ });
+
+ test("cast returns a new QueueList", () {
+ var queue = new QueueList<String>();
+ expect(queue.cast<Pattern>(), isNot(same(queue)));
+ });
}
/// Returns a queue whose internal ring buffer is full enough that adding a new
@@ -262,7 +305,7 @@
/// Returns a queue whose internal tail has a lower index than its head.
QueueList withInternalGap() {
- var queue = new QueueList.from([null, null, null, null, 1, 2, 3, 4]);
+ var queue = new QueueList.from(<dynamic>[null, null, null, null, 1, 2, 3, 4]);
for (var i = 0; i < 4; i++) {
queue.removeFirst();
}
diff --git a/packages/collection/test/typed_wrapper/iterable_test.dart b/packages/collection/test/typed_wrapper/iterable_test.dart
deleted file mode 100644
index 5de9651..0000000
--- a/packages/collection/test/typed_wrapper/iterable_test.dart
+++ /dev/null
@@ -1,354 +0,0 @@
-// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import "package:collection/collection.dart";
-import "package:test/test.dart";
-
-import '../utils.dart';
-
-void main() {
- group("with valid types, forwards", () {
- var wrapper;
- var emptyWrapper;
- var singleWrapper;
- setUp(() {
- wrapper =
- DelegatingIterable.typed<int>(<Object>[1, 2, 3, 4, 5].map((i) => i));
- emptyWrapper = DelegatingIterable.typed<int>(<Object>[].map((i) => i));
- singleWrapper = DelegatingIterable.typed<int>(<Object>[1].map((i) => i));
- });
-
- test("any()", () {
- expect(wrapper.any((i) => i > 3), isTrue);
- expect(wrapper.any((i) => i > 5), isFalse);
- });
-
- test("contains()", () {
- expect(wrapper.contains(2), isTrue);
- expect(wrapper.contains(6), isFalse);
- expect(wrapper.contains("foo"), isFalse);
- });
-
- test("elementAt()", () {
- expect(wrapper.elementAt(1), equals(2));
- expect(wrapper.elementAt(4), equals(5));
- expect(() => wrapper.elementAt(5), throwsRangeError);
- expect(() => wrapper.elementAt(-1), throwsRangeError);
- });
-
- test("every()", () {
- expect(wrapper.every((i) => i < 6), isTrue);
- expect(wrapper.every((i) => i > 3), isFalse);
- });
-
- test("expand()", () {
- expect(wrapper.expand((i) => [i]), equals([1, 2, 3, 4, 5]));
- expect(wrapper.expand((i) => [i, i]),
- equals([1, 1, 2, 2, 3, 3, 4, 4, 5, 5]));
- });
-
- test("first", () {
- expect(wrapper.first, equals(1));
- expect(() => emptyWrapper.first, throwsStateError);
- });
-
- test("firstWhere()", () {
- expect(wrapper.firstWhere((i) => i > 3), equals(4));
- expect(() => wrapper.firstWhere((i) => i > 5), throwsStateError);
- expect(wrapper.firstWhere((i) => i > 5, orElse: () => -1), equals(-1));
- });
-
- test("fold()", () {
- expect(wrapper.fold("", (previous, i) => previous + i.toString()),
- equals("12345"));
- expect(emptyWrapper.fold(null, (previous, i) => previous + i), isNull);
- });
-
- test("forEach()", () {
- var results = [];
- wrapper.forEach(results.add);
- expect(results, equals([1, 2, 3, 4, 5]));
-
- emptyWrapper.forEach(expectAsync1((_) {}, count: 0));
- });
-
- test("isEmpty", () {
- expect(wrapper.isEmpty, isFalse);
- expect(emptyWrapper.isEmpty, isTrue);
- });
-
- test("isNotEmpty", () {
- expect(wrapper.isNotEmpty, isTrue);
- expect(emptyWrapper.isNotEmpty, isFalse);
- });
-
- test("iterator", () {
- var iterator = wrapper.iterator;
- expect(iterator.current, isNull);
- expect(iterator.moveNext(), isTrue);
- expect(iterator.current, equals(1));
- expect(iterator.moveNext(), isTrue);
- expect(iterator.current, equals(2));
- expect(iterator.moveNext(), isTrue);
- expect(iterator.current, equals(3));
- expect(iterator.moveNext(), isTrue);
- expect(iterator.current, equals(4));
- expect(iterator.moveNext(), isTrue);
- expect(iterator.current, equals(5));
- expect(iterator.moveNext(), isFalse);
- expect(iterator.current, isNull);
- });
-
- test("join()", () {
- expect(wrapper.join(), "12345");
- expect(wrapper.join("-"), "1-2-3-4-5");
- });
-
- test("last", () {
- expect(wrapper.last, equals(5));
- expect(() => emptyWrapper.last, throwsStateError);
- });
-
- test("lastWhere()", () {
- expect(wrapper.lastWhere((i) => i > 3), equals(5));
- expect(() => wrapper.lastWhere((i) => i > 5), throwsStateError);
- expect(wrapper.lastWhere((i) => i > 5, orElse: () => -1), equals(-1));
- });
-
- test("length", () {
- expect(wrapper.length, equals(5));
- expect(emptyWrapper.length, equals(0));
- });
-
- test("map()", () {
- expect(wrapper.map((i) => i + 1), equals([2, 3, 4, 5, 6]));
- expect(wrapper.map((i) => i / 2), equals([0.5, 1.0, 1.5, 2.0, 2.5]));
- });
-
- test("reduce()", () {
- expect(wrapper.reduce((value, i) => value + i), equals(15));
- expect(
- () => emptyWrapper.reduce((value, i) => value + i), throwsStateError);
- });
-
- test("single", () {
- expect(() => wrapper.single, throwsStateError);
- expect(singleWrapper.single, equals(1));
- });
-
- test("singleWhere()", () {
- expect(() => wrapper.singleWhere((i) => i.isOdd), throwsStateError);
- expect(singleWrapper.singleWhere((i) => i.isOdd), equals(1));
- expect(
- () => singleWrapper.singleWhere((i) => i.isEven), throwsStateError);
- });
-
- test("skip()", () {
- expect(wrapper.skip(3), equals([4, 5]));
- expect(wrapper.skip(10), isEmpty);
- expect(() => wrapper.skip(-1), throwsRangeError);
- });
-
- test("skipWhile()", () {
- expect(wrapper.skipWhile((i) => i < 3), equals([3, 4, 5]));
- expect(wrapper.skipWhile((i) => i < 10), isEmpty);
- });
-
- test("take()", () {
- expect(wrapper.take(3), equals([1, 2, 3]));
- expect(wrapper.take(10), equals([1, 2, 3, 4, 5]));
- expect(() => wrapper.take(-1), throwsRangeError);
- });
-
- test("takeWhile()", () {
- expect(wrapper.takeWhile((i) => i < 3), equals([1, 2]));
- expect(wrapper.takeWhile((i) => i < 10), equals([1, 2, 3, 4, 5]));
- });
-
- test("toList()", () {
- expect(wrapper.toList(), equals([1, 2, 3, 4, 5]));
- expect(wrapper.toList(growable: false), equals([1, 2, 3, 4, 5]));
- expect(
- () => wrapper.toList(growable: false).add(6), throwsUnsupportedError);
- });
-
- test("toSet()", () {
- expect(wrapper.toSet(), unorderedEquals([1, 2, 3, 4, 5]));
- expect(DelegatingIterable.typed<int>(<Object>[1, 1, 2, 2]).toSet(),
- unorderedEquals([1, 2]));
- });
-
- test("where()", () {
- expect(wrapper.where((i) => i.isOdd), equals([1, 3, 5]));
- expect(wrapper.where((i) => i.isEven), equals([2, 4]));
- });
-
- test("toString()", () {
- expect(wrapper.toString(), equals("(1, 2, 3, 4, 5)"));
- expect(emptyWrapper.toString(), equals("()"));
- });
- });
-
- group("with invalid types", () {
- var wrapper;
- var singleWrapper;
- setUp(() {
- wrapper = DelegatingIterable
- .typed<int>(<Object>["foo", "bar", "baz"].map((element) => element));
- singleWrapper = DelegatingIterable
- .typed<int>(<Object>["foo"].map((element) => element));
- });
-
- group("throws a CastError for", () {
- test("any()", () {
- expect(() => wrapper.any(expectAsync1((_) => false, count: 0)),
- throwsCastError);
- });
-
- test("elementAt()", () {
- expect(() => wrapper.elementAt(1), throwsCastError);
- });
-
- test("every()", () {
- expect(() => wrapper.every(expectAsync1((_) => false, count: 0)),
- throwsCastError);
- });
-
- test("expand()", () {
- var lazy = wrapper.expand(expectAsync1((_) => [], count: 0));
- expect(() => lazy.first, throwsCastError);
- });
-
- test("first", () {
- expect(() => wrapper.first, throwsCastError);
- });
-
- test("firstWhere()", () {
- expect(() => wrapper.firstWhere(expectAsync1((_) => false, count: 0)),
- throwsCastError);
- });
-
- test("fold()", () {
- expect(
- () => wrapper.fold(null, expectAsync2((_, __) => null, count: 0)),
- throwsCastError);
- });
-
- test("forEach()", () {
- expect(() => wrapper.forEach(expectAsync1((_) {}, count: 0)),
- throwsCastError);
- });
-
- test("iterator", () {
- var iterator = wrapper.iterator;
- expect(iterator.current, isNull);
- expect(() => iterator.moveNext(), throwsCastError);
- });
-
- test("last", () {
- expect(() => wrapper.last, throwsCastError);
- });
-
- test("lastWhere()", () {
- expect(() => wrapper.lastWhere(expectAsync1((_) => false, count: 0)),
- throwsCastError);
- });
-
- test("map()", () {
- var lazy = wrapper.map(expectAsync1((_) => null, count: 0));
- expect(() => lazy.first, throwsCastError);
- });
-
- test("reduce()", () {
- expect(() => wrapper.reduce(expectAsync2((_, __) => null, count: 0)),
- throwsCastError);
- });
-
- test("single", () {
- expect(() => singleWrapper.single, throwsCastError);
- });
-
- test("singleWhere()", () {
- expect(() {
- singleWrapper.singleWhere(expectAsync1((_) => false, count: 0));
- }, throwsCastError);
- });
-
- test("skip()", () {
- var lazy = wrapper.skip(1);
- expect(() => lazy.first, throwsCastError);
- });
-
- test("skipWhile()", () {
- var lazy = wrapper.skipWhile(expectAsync1((_) => false, count: 0));
- expect(() => lazy.first, throwsCastError);
- });
-
- test("take()", () {
- var lazy = wrapper.take(1);
- expect(() => lazy.first, throwsCastError);
- });
-
- test("takeWhile()", () {
- var lazy = wrapper.takeWhile(expectAsync1((_) => false, count: 0));
- expect(() => lazy.first, throwsCastError);
- });
-
- test("toList()", () {
- var list = wrapper.toList();
- expect(() => list.first, throwsCastError);
- });
-
- test("toSet()", () {
- var asSet = wrapper.toSet();
- expect(() => asSet.first, throwsCastError);
- });
-
- test("where()", () {
- var lazy = wrapper.where(expectAsync1((_) => false, count: 0));
- expect(() => lazy.first, throwsCastError);
- });
- });
-
- group("doesn't throw a CastError for", () {
- test("contains()", () {
- expect(wrapper.contains(1), isFalse);
- expect(wrapper.contains("foo"), isTrue);
- });
-
- test("elementAt()", () {
- expect(() => wrapper.elementAt(-1), throwsRangeError);
- expect(() => wrapper.elementAt(10), throwsRangeError);
- });
-
- test("isEmpty", () {
- expect(wrapper.isEmpty, isFalse);
- });
-
- test("isNotEmpty", () {
- expect(wrapper.isNotEmpty, isTrue);
- });
-
- test("join()", () {
- expect(wrapper.join(), "foobarbaz");
- });
-
- test("length", () {
- expect(wrapper.length, equals(3));
- });
-
- test("single", () {
- expect(() => wrapper.single, throwsStateError);
- });
-
- test("skip()", () {
- expect(() => wrapper.skip(-1), throwsRangeError);
- });
-
- test("toString()", () {
- expect(wrapper.toString(), equals("(foo, bar, baz)"));
- });
- });
- }, skip: "Re-enable this when test can run DDC (test#414).");
-}
diff --git a/packages/collection/test/typed_wrapper/list_test.dart b/packages/collection/test/typed_wrapper/list_test.dart
deleted file mode 100644
index 7876dbe..0000000
--- a/packages/collection/test/typed_wrapper/list_test.dart
+++ /dev/null
@@ -1,421 +0,0 @@
-// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import 'dart:math' as math;
-
-import "package:collection/collection.dart";
-import "package:test/test.dart";
-
-import '../utils.dart';
-
-void main() {
- group("with valid types, forwards", () {
- var wrapper;
- var emptyWrapper;
- setUp(() {
- wrapper = DelegatingList.typed<int>(<Object>[1, 2, 3, 4, 5]);
- emptyWrapper = DelegatingList.typed<int>(<Object>[]);
- });
-
- test("[]", () {
- expect(wrapper[0], equals(1));
- expect(wrapper[1], equals(2));
- expect(() => wrapper[-1], throwsRangeError);
- expect(() => wrapper[10], throwsRangeError);
- });
-
- test("[]=", () {
- wrapper[1] = 10;
- wrapper[3] = 15;
- expect(wrapper, equals([1, 10, 3, 15, 5]));
- expect(() => wrapper[-1] = 10, throwsRangeError);
- expect(() => wrapper[10] = 10, throwsRangeError);
- });
-
- test("add()", () {
- wrapper.add(6);
- wrapper.add(7);
- expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7]));
- });
-
- test("addAll()", () {
- wrapper.addAll([6, 7, 8]);
- expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7, 8]));
- });
-
- test("asMap()", () {
- expect(wrapper.asMap(), equals({0: 1, 1: 2, 2: 3, 3: 4, 4: 5}));
- });
-
- test("clear()", () {
- wrapper.clear();
- expect(wrapper, isEmpty);
- });
-
- test("fillRange()", () {
- wrapper.fillRange(2, 4);
- expect(wrapper, equals([1, 2, null, null, 5]));
-
- wrapper.fillRange(1, 2, 7);
- expect(wrapper, equals([1, 7, null, null, 5]));
-
- expect(() => wrapper.fillRange(-1, 2), throwsRangeError);
- expect(() => wrapper.fillRange(1, 10), throwsRangeError);
- expect(() => wrapper.fillRange(4, 2), throwsRangeError);
- expect(() => wrapper.fillRange(10, 12), throwsRangeError);
- });
-
- test("getRange()", () {
- expect(wrapper.getRange(2, 4), equals([3, 4]));
- expect(wrapper.getRange(1, 2), equals([2]));
-
- expect(() => wrapper.getRange(-1, 2), throwsRangeError);
- expect(() => wrapper.getRange(1, 10), throwsRangeError);
- expect(() => wrapper.getRange(4, 2), throwsRangeError);
- expect(() => wrapper.getRange(10, 12), throwsRangeError);
- });
-
- test("indexOf()", () {
- expect(wrapper.indexOf(4), equals(3));
- expect(wrapper.indexOf(10), equals(-1));
- expect(wrapper.indexOf(4, 2), equals(3));
- expect(wrapper.indexOf(4, 4), equals(-1));
- });
-
- test("insert()", () {
- wrapper.insert(3, 10);
- expect(wrapper, equals([1, 2, 3, 10, 4, 5]));
-
- wrapper.insert(0, 15);
- expect(wrapper, equals([15, 1, 2, 3, 10, 4, 5]));
-
- expect(() => wrapper.insert(-1, 0), throwsRangeError);
- expect(() => wrapper.insert(10, 0), throwsRangeError);
- });
-
- test("insertAll()", () {
- wrapper.insertAll(3, [10, 11, 12]);
- expect(wrapper, equals([1, 2, 3, 10, 11, 12, 4, 5]));
-
- wrapper.insertAll(0, [15, 16, 17]);
- expect(wrapper, equals([15, 16, 17, 1, 2, 3, 10, 11, 12, 4, 5]));
-
- expect(() => wrapper.insertAll(-1, []), throwsRangeError);
- expect(() => wrapper.insertAll(100, []), throwsRangeError);
- });
-
- test("lastIndexOf()", () {
- expect(wrapper.lastIndexOf(4), equals(3));
- expect(wrapper.lastIndexOf(10), equals(-1));
- expect(wrapper.lastIndexOf(4, 4), equals(3));
- expect(wrapper.lastIndexOf(4, 2), equals(-1));
- });
-
- test("length=", () {
- wrapper.length = 10;
- expect(wrapper, equals([1, 2, 3, 4, 5, null, null, null, null, null]));
-
- wrapper.length = 3;
- expect(wrapper, equals([1, 2, 3]));
- });
-
- test("remove()", () {
- expect(wrapper.remove(3), isTrue);
- expect(wrapper, equals([1, 2, 4, 5]));
-
- expect(wrapper.remove(3), isFalse);
- expect(wrapper.remove("foo"), isFalse);
- });
-
- test("removeAt()", () {
- expect(wrapper.removeAt(3), equals(4));
- expect(wrapper, equals([1, 2, 3, 5]));
-
- expect(() => wrapper.removeAt(-1), throwsRangeError);
- expect(() => wrapper.removeAt(10), throwsRangeError);
- });
-
- test("removeLast()", () {
- expect(wrapper.removeLast(), equals(5));
- expect(wrapper, equals([1, 2, 3, 4]));
-
- // See sdk#26087. We want this to pass with the current implementation and
- // with the fix.
- expect(() => emptyWrapper.removeLast(),
- anyOf(throwsStateError, throwsRangeError));
- });
-
- test("removeRange()", () {
- wrapper.removeRange(2, 4);
- expect(wrapper, equals([1, 2, 5]));
-
- expect(() => wrapper.removeRange(-1, 2), throwsRangeError);
- expect(() => wrapper.removeRange(1, 10), throwsRangeError);
- expect(() => wrapper.removeRange(4, 2), throwsRangeError);
- expect(() => wrapper.removeRange(10, 12), throwsRangeError);
- });
-
- test("removeWhere()", () {
- wrapper.removeWhere((i) => i.isOdd);
- expect(wrapper, equals([2, 4]));
- });
-
- test("replaceRange()", () {
- wrapper.replaceRange(2, 4, [10, 11, 12]);
- expect(wrapper, equals([1, 2, 10, 11, 12, 5]));
-
- expect(() => wrapper.replaceRange(-1, 2, []), throwsRangeError);
- expect(() => wrapper.replaceRange(1, 10, []), throwsRangeError);
- expect(() => wrapper.replaceRange(4, 2, []), throwsRangeError);
- expect(() => wrapper.replaceRange(10, 12, []), throwsRangeError);
- });
-
- test("retainWhere()", () {
- wrapper.retainWhere((i) => i.isOdd);
- expect(wrapper, equals([1, 3, 5]));
- });
-
- test("reversed", () {
- expect(wrapper.reversed, equals([5, 4, 3, 2, 1]));
- });
-
- test("setAll()", () {
- wrapper.setAll(2, [10, 11]);
- expect(wrapper, equals([1, 2, 10, 11, 5]));
-
- expect(() => wrapper.setAll(-1, []), throwsRangeError);
- expect(() => wrapper.setAll(10, []), throwsRangeError);
- });
-
- test("setRange()", () {
- wrapper.setRange(2, 4, [10, 11, 12]);
- expect(wrapper, equals([1, 2, 10, 11, 5]));
-
- wrapper.setRange(2, 4, [10, 11, 12], 1);
- expect(wrapper, equals([1, 2, 11, 12, 5]));
-
- expect(() => wrapper.setRange(-1, 2, []), throwsRangeError);
- expect(() => wrapper.setRange(1, 10, []), throwsRangeError);
- expect(() => wrapper.setRange(4, 2, []), throwsRangeError);
- expect(() => wrapper.setRange(10, 12, []), throwsRangeError);
- expect(() => wrapper.setRange(2, 4, []), throwsStateError);
- });
-
- test("shuffle()", () {
- wrapper.shuffle(new math.Random(1234));
- expect(wrapper, equals([1, 2, 3, 4, 5]..shuffle(new math.Random(1234))));
- });
-
- test("sort()", () {
- wrapper.sort((a, b) => b.compareTo(a));
- expect(wrapper, equals([5, 4, 3, 2, 1]));
-
- wrapper.sort();
- expect(wrapper, equals([1, 2, 3, 4, 5]));
- });
-
- test("sublist()", () {
- expect(wrapper.sublist(2), equals([3, 4, 5]));
- expect(wrapper.sublist(2, 4), equals([3, 4]));
- });
- });
-
- group("with invalid types", () {
- List inner;
- var wrapper;
- setUp(() {
- inner = <Object>["foo", "bar", "baz"];
- wrapper = DelegatingList.typed<int>(inner);
- });
-
- group("throws a CastError for", () {
- test("[]", () {
- expect(() => wrapper[0], throwsCastError);
- });
-
- test("asMap()", () {
- var map = wrapper.asMap();
- expect(() => map[1], throwsCastError);
- });
-
- test("getRange()", () {
- var lazy = wrapper.getRange(1, 2);
- expect(() => lazy.first, throwsCastError);
- });
-
- test("removeAt()", () {
- expect(() => wrapper.removeAt(2), throwsCastError);
- });
-
- test("removeLast()", () {
- expect(() => wrapper.removeLast(), throwsCastError);
- });
-
- test("removeWhere()", () {
- expect(() => wrapper.removeWhere(expectAsync1((_) => false, count: 0)),
- throwsCastError);
- });
-
- test("retainWhere()", () {
- expect(() => wrapper.retainWhere(expectAsync1((_) => false, count: 0)),
- throwsCastError);
- });
-
- test("reversed", () {
- var lazy = wrapper.reversed;
- expect(() => lazy.first, throwsCastError);
- });
-
- test("sort()", () {
- expect(() => wrapper.sort(expectAsync2((_, __) => 0, count: 0)),
- throwsCastError);
- });
-
- test("sublist()", () {
- var list = wrapper.sublist(1);
- expect(() => list[0], throwsCastError);
- });
- });
-
- group("doesn't throw a CastError for", () {
- test("[]", () {
- expect(() => wrapper[-1], throwsRangeError);
- expect(() => wrapper[10], throwsRangeError);
- });
-
- test("[]=", () {
- wrapper[1] = 10;
- expect(inner, equals(["foo", 10, "baz"]));
- expect(() => wrapper[-1] = 10, throwsRangeError);
- expect(() => wrapper[10] = 10, throwsRangeError);
- });
-
- test("add()", () {
- wrapper.add(6);
- wrapper.add(7);
- expect(inner, equals(["foo", "bar", "baz", 6, 7]));
- });
-
- test("addAll()", () {
- wrapper.addAll([6, 7, 8]);
- expect(inner, equals(["foo", "bar", "baz", 6, 7, 8]));
- });
-
- test("clear()", () {
- wrapper.clear();
- expect(wrapper, isEmpty);
- });
-
- test("fillRange()", () {
- wrapper.fillRange(1, 3, 7);
- expect(inner, equals(["foo", 7, 7]));
-
- expect(() => wrapper.fillRange(-1, 2), throwsRangeError);
- expect(() => wrapper.fillRange(1, 10), throwsRangeError);
- expect(() => wrapper.fillRange(4, 2), throwsRangeError);
- expect(() => wrapper.fillRange(10, 12), throwsRangeError);
- });
-
- test("getRange()", () {
- expect(() => wrapper.getRange(-1, 2), throwsRangeError);
- expect(() => wrapper.getRange(1, 10), throwsRangeError);
- expect(() => wrapper.getRange(4, 2), throwsRangeError);
- expect(() => wrapper.getRange(10, 12), throwsRangeError);
- });
-
- test("indexOf()", () {
- expect(wrapper.indexOf(4), equals(-1));
- });
-
- test("insert()", () {
- wrapper.insert(0, 15);
- expect(inner, equals([15, "foo", "bar", "baz"]));
-
- expect(() => wrapper.insert(-1, 0), throwsRangeError);
- expect(() => wrapper.insert(10, 0), throwsRangeError);
- });
-
- test("insertAll()", () {
- wrapper.insertAll(0, [15, 16, 17]);
- expect(inner, equals([15, 16, 17, "foo", "bar", "baz"]));
-
- expect(() => wrapper.insertAll(-1, []), throwsRangeError);
- expect(() => wrapper.insertAll(100, []), throwsRangeError);
- });
-
- test("lastIndexOf()", () {
- expect(wrapper.lastIndexOf(4), equals(-1));
- });
-
- test("length=", () {
- wrapper.length = 5;
- expect(inner, equals(["foo", "bar", "baz", null, null]));
-
- wrapper.length = 1;
- expect(inner, equals(["foo"]));
- });
-
- test("remove()", () {
- expect(wrapper.remove(3), isFalse);
- expect(wrapper.remove("foo"), isTrue);
- expect(inner, equals(["bar", "baz"]));
- });
-
- test("removeAt()", () {
- expect(() => wrapper.removeAt(-1), throwsRangeError);
- expect(() => wrapper.removeAt(10), throwsRangeError);
- });
-
- test("removeRange()", () {
- wrapper.removeRange(1, 3);
- expect(inner, equals(["foo"]));
-
- expect(() => wrapper.removeRange(-1, 2), throwsRangeError);
- expect(() => wrapper.removeRange(1, 10), throwsRangeError);
- expect(() => wrapper.removeRange(4, 2), throwsRangeError);
- expect(() => wrapper.removeRange(10, 12), throwsRangeError);
- });
-
- test("replaceRange()", () {
- wrapper.replaceRange(1, 2, [10, 11, 12]);
- expect(inner, equals(["foo", 10, 11, 12, "baz"]));
-
- expect(() => wrapper.replaceRange(-1, 2, []), throwsRangeError);
- expect(() => wrapper.replaceRange(1, 10, []), throwsRangeError);
- expect(() => wrapper.replaceRange(4, 2, []), throwsRangeError);
- expect(() => wrapper.replaceRange(10, 12, []), throwsRangeError);
- });
-
- test("setAll()", () {
- wrapper.setAll(1, [10, 11]);
- expect(inner, equals(["foo", 10, 11]));
-
- expect(() => wrapper.setAll(-1, []), throwsRangeError);
- expect(() => wrapper.setAll(10, []), throwsRangeError);
- });
-
- test("setRange()", () {
- wrapper.setRange(1, 2, [10, 11, 12]);
- expect(inner, equals(["foo", 10, "baz"]));
-
- expect(() => wrapper.setRange(-1, 2, []), throwsRangeError);
- expect(() => wrapper.setRange(1, 10, []), throwsRangeError);
- expect(() => wrapper.setRange(4, 2, []), throwsRangeError);
- expect(() => wrapper.setRange(10, 12, []), throwsRangeError);
- expect(() => wrapper.setRange(1, 2, []), throwsStateError);
- });
-
- test("shuffle()", () {
- wrapper.shuffle(new math.Random(1234));
- expect(inner,
- equals(["foo", "bar", "baz"]..shuffle(new math.Random(1234))));
- });
-
- test("sort()", () {
- wrapper.sort();
- expect(inner, equals(["bar", "baz", "foo"]));
- });
- });
- }, skip: "Re-enable this when test can run DDC (test#414).");
-}
diff --git a/packages/collection/test/typed_wrapper/map_test.dart b/packages/collection/test/typed_wrapper/map_test.dart
deleted file mode 100644
index 75078bd..0000000
--- a/packages/collection/test/typed_wrapper/map_test.dart
+++ /dev/null
@@ -1,327 +0,0 @@
-// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import "package:collection/collection.dart";
-import "package:test/test.dart";
-
-import '../utils.dart';
-
-void main() {
- group("with valid types, forwards", () {
- var wrapper;
- var emptyWrapper;
- setUp(() {
- wrapper = DelegatingMap.typed<String, int>(
- <Object, Object>{"foo": 1, "bar": 2, "baz": 3, "bang": 4});
- emptyWrapper = DelegatingMap.typed<String, int>(<Object, Object>{});
- });
-
- test("[]", () {
- expect(wrapper["foo"], equals(1));
- expect(wrapper["bar"], equals(2));
- expect(wrapper["qux"], isNull);
- expect(wrapper[1], isNull);
- });
-
- test("[]=", () {
- wrapper["foo"] = 5;
- expect(wrapper, equals({"foo": 5, "bar": 2, "baz": 3, "bang": 4}));
-
- wrapper["qux"] = 6;
- expect(
- wrapper, equals({"foo": 5, "bar": 2, "baz": 3, "bang": 4, "qux": 6}));
- });
-
- test("addAll()", () {
- wrapper.addAll({"bar": 5, "qux": 6});
- expect(
- wrapper, equals({"foo": 1, "bar": 5, "baz": 3, "bang": 4, "qux": 6}));
- });
-
- test("clear()", () {
- wrapper.clear();
- expect(wrapper, isEmpty);
- });
-
- test("containsKey()", () {
- expect(wrapper.containsKey("foo"), isTrue);
- expect(wrapper.containsKey("qux"), isFalse);
- expect(wrapper.containsKey(1), isFalse);
- });
-
- test("containsValue()", () {
- expect(wrapper.containsValue(1), isTrue);
- expect(wrapper.containsValue(7), isFalse);
- expect(wrapper.containsValue("foo"), isFalse);
- });
-
- test("forEach()", () {
- var results = [];
- wrapper.forEach((key, value) => results.add([key, value]));
- expect(
- results,
- unorderedEquals([
- ["foo", 1],
- ["bar", 2],
- ["baz", 3],
- ["bang", 4]
- ]));
-
- emptyWrapper.forEach(expectAsync2((_, __) {}, count: 0));
- });
-
- test("isEmpty", () {
- expect(wrapper.isEmpty, isFalse);
- expect(emptyWrapper.isEmpty, isTrue);
- });
-
- test("isNotEmpty", () {
- expect(wrapper.isNotEmpty, isTrue);
- expect(emptyWrapper.isNotEmpty, isFalse);
- });
-
- test("keys", () {
- expect(wrapper.keys, unorderedEquals(["foo", "bar", "baz", "bang"]));
- expect(emptyWrapper.keys, isEmpty);
- });
-
- test("length", () {
- expect(wrapper.length, equals(4));
- expect(emptyWrapper.length, equals(0));
- });
-
- test("putIfAbsent()", () {
- expect(wrapper.putIfAbsent("foo", expectAsync1((_) => null, count: 0)),
- equals(1));
-
- expect(wrapper.putIfAbsent("qux", () => 6), equals(6));
- expect(
- wrapper, equals({"foo": 1, "bar": 2, "baz": 3, "bang": 4, "qux": 6}));
- });
-
- test("remove()", () {
- expect(wrapper.remove("foo"), equals(1));
- expect(wrapper, equals({"bar": 2, "baz": 3, "bang": 4}));
-
- expect(wrapper.remove("foo"), isNull);
- expect(wrapper.remove(3), isNull);
- });
-
- test("values", () {
- expect(wrapper.values, unorderedEquals([1, 2, 3, 4]));
- expect(emptyWrapper.values, isEmpty);
- });
-
- test("toString()", () {
- expect(
- wrapper.toString(),
- allOf([
- startsWith("{"),
- contains("foo: 1"),
- contains("bar: 2"),
- contains("baz: 3"),
- contains("bang: 4"),
- endsWith("}")
- ]));
- });
- });
-
- group("with invalid key types", () {
- Map inner;
- var wrapper;
- setUp(() {
- inner = <Object, Object>{1: 1, 2: 2, 3: 3, 4: 4};
- wrapper = DelegatingMap.typed<String, int>(inner);
- });
-
- group("throws a CastError for", () {
- test("forEach()", () {
- expect(() => wrapper.forEach(expectAsync2((_, __) {}, count: 0)),
- throwsCastError);
- });
-
- test("keys", () {
- var lazy = wrapper.keys;
- expect(() => lazy.first, throwsCastError);
- });
- });
-
- group("doesn't throw a CastError for", () {
- test("[]", () {
- expect(wrapper["foo"], isNull);
- expect(wrapper[1], equals(1));
- expect(wrapper[7], isNull);
- });
-
- test("[]=", () {
- wrapper["foo"] = 5;
- expect(inner, equals({"foo": 5, 1: 1, 2: 2, 3: 3, 4: 4}));
- });
-
- test("addAll()", () {
- wrapper.addAll({"foo": 1, "bar": 2});
- expect(inner, equals({"foo": 1, "bar": 2, 1: 1, 2: 2, 3: 3, 4: 4}));
- });
-
- test("clear()", () {
- wrapper.clear();
- expect(wrapper, isEmpty);
- });
-
- test("containsKey()", () {
- expect(wrapper.containsKey(1), isTrue);
- expect(wrapper.containsKey(7), isFalse);
- expect(wrapper.containsKey("foo"), isFalse);
- });
-
- test("containsValue()", () {
- expect(wrapper.containsValue(1), isTrue);
- expect(wrapper.containsValue(7), isFalse);
- expect(wrapper.containsValue("foo"), isFalse);
- });
-
- test("isEmpty", () {
- expect(wrapper.isEmpty, isFalse);
- });
-
- test("isNotEmpty", () {
- expect(wrapper.isNotEmpty, isTrue);
- });
-
- test("length", () {
- expect(wrapper.length, equals(4));
- });
-
- test("putIfAbsent()", () {
- expect(wrapper.putIfAbsent("foo", () => 1), equals(1));
- expect(inner, equals({"foo": 1, 1: 1, 2: 2, 3: 3, 4: 4}));
- });
-
- test("remove()", () {
- expect(wrapper.remove(1), equals(1));
- expect(inner, equals({2: 2, 3: 3, 4: 4}));
-
- expect(wrapper.remove("foo"), isNull);
- expect(wrapper.remove(7), isNull);
- });
-
- test("values", () {
- expect(wrapper.values, unorderedEquals([1, 2, 3, 4]));
- });
-
- test("toString()", () {
- expect(
- wrapper.toString(),
- allOf([
- startsWith("{"),
- contains("1: 1"),
- contains("2: 2"),
- contains("3: 3"),
- contains("4: 4"),
- endsWith("}")
- ]));
- });
- });
- }, skip: "Re-enable this when test can run DDC (test#414).");
-
- group("with invalid value types", () {
- Map inner;
- var wrapper;
- setUp(() {
- inner = <Object, Object>{"foo": "bar", "baz": "bang"};
- wrapper = DelegatingMap.typed<String, int>(inner);
- });
-
- group("throws a CastError for", () {
- test("forEach()", () {
- expect(() => wrapper.forEach(expectAsync2((_, __) {}, count: 0)),
- throwsCastError);
- });
-
- test("[]", () {
- expect(() => wrapper["foo"], throwsCastError);
- expect(wrapper["qux"], isNull);
- });
-
- test("putIfAbsent()", () {
- expect(() => wrapper.putIfAbsent("foo", () => 1), throwsCastError);
- });
-
- test("remove()", () {
- expect(() => wrapper.remove("foo"), throwsCastError);
- });
-
- test("values", () {
- var lazy = wrapper.values;
- expect(() => lazy.first, throwsCastError);
- });
- });
-
- group("doesn't throw a CastError for", () {
- test("[]=", () {
- wrapper["foo"] = 5;
- expect(inner, equals({"foo": 5, "baz": "bang"}));
- });
-
- test("addAll()", () {
- wrapper.addAll({"foo": 1, "qux": 2});
- expect(inner, equals({"foo": 1, "baz": "bang", "qux": 2}));
- });
-
- test("clear()", () {
- wrapper.clear();
- expect(wrapper, isEmpty);
- });
-
- test("containsKey()", () {
- expect(wrapper.containsKey("foo"), isTrue);
- expect(wrapper.containsKey(1), isFalse);
- expect(wrapper.containsKey("qux"), isFalse);
- });
-
- test("containsValue()", () {
- expect(wrapper.containsValue("bar"), isTrue);
- expect(wrapper.containsValue(1), isFalse);
- expect(wrapper.containsValue("foo"), isFalse);
- });
-
- test("isEmpty", () {
- expect(wrapper.isEmpty, isFalse);
- });
-
- test("isNotEmpty", () {
- expect(wrapper.isNotEmpty, isTrue);
- });
-
- test("keys", () {
- expect(wrapper.keys, unorderedEquals(["foo", "baz"]));
- });
-
- test("length", () {
- expect(wrapper.length, equals(2));
- });
-
- test("putIfAbsent()", () {
- expect(wrapper.putIfAbsent("qux", () => 1), equals(1));
- expect(inner, equals({"foo": "bar", "baz": "bang", "qux": 1}));
- });
-
- test("remove()", () {
- expect(wrapper.remove("qux"), isNull);
- expect(wrapper.remove(7), isNull);
- });
-
- test("toString()", () {
- expect(
- wrapper.toString(),
- allOf([
- startsWith("{"),
- contains("foo: bar"),
- contains("baz: bang"),
- endsWith("}")
- ]));
- });
- });
- }, skip: "Re-enable this when test can run DDC (test#414).");
-}
diff --git a/packages/collection/test/typed_wrapper/queue_test.dart b/packages/collection/test/typed_wrapper/queue_test.dart
deleted file mode 100644
index e9ffb3a..0000000
--- a/packages/collection/test/typed_wrapper/queue_test.dart
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import "dart:collection";
-
-import "package:collection/collection.dart";
-import "package:test/test.dart";
-
-import '../utils.dart';
-
-void main() {
- group("with valid types, forwards", () {
- var wrapper;
- var emptyWrapper;
- setUp(() {
- wrapper =
- DelegatingQueue.typed<int>(new Queue<Object>.from([1, 2, 3, 4, 5]));
- emptyWrapper = DelegatingQueue.typed<int>(new Queue<Object>());
- });
-
- test("add()", () {
- wrapper.add(6);
- wrapper.add(7);
- expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7]));
- });
-
- test("addAll()", () {
- wrapper.addAll([6, 7, 8]);
- expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7, 8]));
- });
-
- test("addFirst()", () {
- wrapper.addFirst(6);
- wrapper.addFirst(7);
- expect(wrapper, equals([7, 6, 1, 2, 3, 4, 5]));
- });
-
- test("addLast()", () {
- wrapper.addLast(6);
- wrapper.addLast(7);
- expect(wrapper, equals([1, 2, 3, 4, 5, 6, 7]));
- });
-
- test("clear()", () {
- wrapper.clear();
- expect(wrapper, isEmpty);
- });
-
- test("remove()", () {
- expect(wrapper.remove(3), isTrue);
- expect(wrapper, equals([1, 2, 4, 5]));
-
- expect(wrapper.remove(3), isFalse);
- expect(wrapper.remove("foo"), isFalse);
- });
-
- test("removeWhere()", () {
- wrapper.removeWhere((i) => i.isOdd);
- expect(wrapper, equals([2, 4]));
- });
-
- test("retainWhere()", () {
- wrapper.retainWhere((i) => i.isOdd);
- expect(wrapper, equals([1, 3, 5]));
- });
-
- test("removeLast()", () {
- expect(wrapper.removeLast(), equals(5));
- expect(wrapper, equals([1, 2, 3, 4]));
-
- expect(() => emptyWrapper.removeLast(), throwsStateError);
- });
-
- test("removeFirst()", () {
- expect(wrapper.removeFirst(), equals(1));
- expect(wrapper, equals([2, 3, 4, 5]));
-
- expect(() => emptyWrapper.removeFirst(), throwsStateError);
- });
- });
-
- group("with invalid types", () {
- Queue inner;
- var wrapper;
- setUp(() {
- inner = new Queue<Object>.from(["foo", "bar", "baz"]);
- wrapper = DelegatingQueue.typed<int>(inner);
- });
-
- group("throws a CastError for", () {
- test("removeLast()", () {
- expect(() => wrapper.removeLast(), throwsCastError);
- });
-
- test("removeFirst()", () {
- expect(() => wrapper.removeFirst(), throwsCastError);
- });
-
- test("removeWhere()", () {
- expect(() => wrapper.removeWhere(expectAsync1((_) => false, count: 0)),
- throwsCastError);
- });
-
- test("retainWhere()", () {
- expect(() => wrapper.retainWhere(expectAsync1((_) => false, count: 0)),
- throwsCastError);
- });
- });
-
- group("doesn't throw a CastError for", () {
- test("add()", () {
- wrapper.add(6);
- wrapper.add(7);
- expect(inner, equals(["foo", "bar", "baz", 6, 7]));
- });
-
- test("addAll()", () {
- wrapper.addAll([6, 7, 8]);
- expect(inner, equals(["foo", "bar", "baz", 6, 7, 8]));
- });
-
- test("addFirst()", () {
- wrapper.addFirst(6);
- wrapper.addFirst(7);
- expect(inner, equals([7, 6, "foo", "bar", "baz"]));
- });
-
- test("addLast()", () {
- wrapper.addLast(6);
- wrapper.addLast(7);
- expect(inner, equals(["foo", "bar", "baz", 6, 7]));
- });
-
- test("clear()", () {
- wrapper.clear();
- expect(wrapper, isEmpty);
- });
- });
- }, skip: "Re-enable this when test can run DDC (test#414).");
-}
diff --git a/packages/collection/test/typed_wrapper/set_test.dart b/packages/collection/test/typed_wrapper/set_test.dart
deleted file mode 100644
index d7eed5f..0000000
--- a/packages/collection/test/typed_wrapper/set_test.dart
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-import "package:collection/collection.dart";
-import "package:test/test.dart";
-
-import '../utils.dart';
-
-void main() {
- group("with valid types, forwards", () {
- var wrapper;
- setUp(() {
- wrapper = DelegatingSet.typed<int>(new Set<Object>.from([1, 2, 3, 4, 5]));
- });
-
- test("add()", () {
- wrapper.add(1);
- wrapper.add(6);
- expect(wrapper, unorderedEquals([1, 2, 3, 4, 5, 6]));
- });
-
- test("addAll()", () {
- wrapper.addAll([1, 6, 7]);
- expect(wrapper, unorderedEquals([1, 2, 3, 4, 5, 6, 7]));
- });
-
- test("clear()", () {
- wrapper.clear();
- expect(wrapper, isEmpty);
- });
-
- test("containsAll()", () {
- expect(wrapper.containsAll([1, 3, 5]), isTrue);
- expect(wrapper.containsAll([1, 3, 6]), isFalse);
- expect(wrapper.containsAll([1, 3, "foo"]), isFalse);
- });
-
- test("difference()", () {
- expect(wrapper.difference(new Set.from([1, 3, 6])),
- unorderedEquals([2, 4, 5]));
- });
-
- test("intersection()", () {
- expect(wrapper.intersection(new Set.from([1, 3, 6, "foo"])),
- unorderedEquals([1, 3]));
- });
-
- test("lookup()", () {
- expect(wrapper.lookup(1), equals(1));
- expect(wrapper.lookup(7), isNull);
- expect(wrapper.lookup("foo"), isNull);
- });
-
- test("remove()", () {
- expect(wrapper.remove(3), isTrue);
- expect(wrapper, unorderedEquals([1, 2, 4, 5]));
-
- expect(wrapper.remove(3), isFalse);
- expect(wrapper.remove("foo"), isFalse);
- });
-
- test("removeAll()", () {
- wrapper.removeAll([1, 3, 6, "foo"]);
- expect(wrapper, unorderedEquals([2, 4, 5]));
- });
-
- test("removeWhere()", () {
- wrapper.removeWhere((i) => i.isOdd);
- expect(wrapper, unorderedEquals([2, 4]));
- });
-
- test("retainAll()", () {
- wrapper.retainAll([1, 3, 6, "foo"]);
- expect(wrapper, unorderedEquals([1, 3]));
- });
-
- test("retainWhere()", () {
- wrapper.retainWhere((i) => i.isOdd);
- expect(wrapper, unorderedEquals([1, 3, 5]));
- });
-
- test("union()", () {
- expect(wrapper.union(new Set.from([5, 6, 7])),
- unorderedEquals([1, 2, 3, 4, 5, 6, 7]));
- });
- });
-
- group("with invalid types", () {
- Set inner;
- var wrapper;
- setUp(() {
- inner = new Set<Object>.from(["foo", "bar", "baz"]);
- wrapper = DelegatingSet.typed<int>(inner);
- });
-
- group("throws a CastError for", () {
- test("difference()", () {
- var result = wrapper.difference(new Set.from([1, 3, 6]));
- expect(() => result.first, throwsCastError);
- });
-
- test("intersection()", () {
- var result = wrapper.intersection(new Set.from([1, 3, 6, "foo"]));
- expect(() => result.first, throwsCastError);
- });
-
- test("lookup()", () {
- expect(() => wrapper.lookup("foo"), throwsCastError);
- });
-
- test("removeWhere()", () {
- expect(() => wrapper.removeWhere(expectAsync1((_) => false, count: 0)),
- throwsCastError);
- });
-
- test("retainWhere()", () {
- expect(() => wrapper.retainWhere(expectAsync1((_) => false, count: 0)),
- throwsCastError);
- });
-
- test("union()", () {
- var result = wrapper.union(new Set.from([5, 6, 7]));
- expect(() => result.first, throwsCastError);
- });
- });
-
- group("doesn't throw a CastError for", () {
- test("add()", () {
- wrapper.add(6);
- expect(inner, unorderedEquals(["foo", "bar", "baz", 6]));
- });
-
- test("addAll()", () {
- wrapper.addAll([6, 7]);
- expect(inner, unorderedEquals(["foo", "bar", "baz", 6, 7]));
- });
-
- test("clear()", () {
- wrapper.clear();
- expect(wrapper, isEmpty);
- });
-
- test("containsAll()", () {
- expect(wrapper.containsAll(["foo", "bar"]), isTrue);
- expect(wrapper.containsAll(["foo", "bar", 1]), isFalse);
- });
-
- test("lookup()", () {
- expect(wrapper.lookup(1), isNull);
- expect(wrapper.lookup("zap"), isNull);
- });
-
- test("remove()", () {
- expect(wrapper.remove("foo"), isTrue);
- expect(inner, unorderedEquals(["bar", "baz"]));
-
- expect(wrapper.remove(3), isFalse);
- expect(wrapper.remove("foo"), isFalse);
- });
-
- test("removeAll()", () {
- wrapper.removeAll([1, "foo", "baz"]);
- expect(inner, unorderedEquals(["bar"]));
- });
-
- test("retainAll()", () {
- wrapper.retainAll([1, "foo", "baz"]);
- expect(inner, unorderedEquals(["foo", "baz"]));
- });
- });
- }, skip: "Re-enable this when test can run DDC (test#414).");
-}
diff --git a/packages/collection/test/union_set_controller_test.dart b/packages/collection/test/union_set_controller_test.dart
index 366b781..8c561d3 100644
--- a/packages/collection/test/union_set_controller_test.dart
+++ b/packages/collection/test/union_set_controller_test.dart
@@ -7,11 +7,11 @@
import "package:collection/collection.dart";
void main() {
- var controller;
+ UnionSetController<int> controller;
Set<int> innerSet;
setUp(() {
innerSet = new Set.from([1, 2, 3]);
- controller = new UnionSetController<int>()..add(innerSet);
+ controller = new UnionSetController()..add(innerSet);
});
test("exposes a union set", () {
diff --git a/packages/collection/test/union_set_test.dart b/packages/collection/test/union_set_test.dart
index f2a792a..d0437ff 100644
--- a/packages/collection/test/union_set_test.dart
+++ b/packages/collection/test/union_set_test.dart
@@ -42,10 +42,10 @@
group("with multiple disjoint sets", () {
var set;
setUp(() {
- set = new UnionSet<int>.from([
- new Set.from([1, 2]),
- new Set.from([3, 4]),
- new Set.from([5]),
+ set = new UnionSet.from([
+ new Set.of([1, 2]),
+ new Set.of([3, 4]),
+ new Set.of([5]),
new Set()
], disjoint: true);
});
@@ -81,10 +81,10 @@
group("with multiple overlapping sets", () {
var set;
setUp(() {
- set = new UnionSet<int>.from([
- new Set.from([1, 2, 3]),
- new Set.from([3, 4]),
- new Set.from([5, 1]),
+ set = new UnionSet.from([
+ new Set.of([1, 2, 3]),
+ new Set.of([3, 4]),
+ new Set.of([5, 1]),
new Set()
]);
});
@@ -114,8 +114,8 @@
expect(duration1, isNot(same(duration2)));
var set = new UnionSet.from([
- new Set.from([duration1]),
- new Set.from([duration2])
+ new Set.of([duration1]),
+ new Set.of([duration2])
]);
expect(set.lookup(new Duration(seconds: 0)), same(duration1));
@@ -134,10 +134,10 @@
group("after an inner set was modified", () {
var set;
setUp(() {
- var innerSet = new Set<int>.from([3, 7]);
- set = new UnionSet<int>.from([
- new Set.from([1, 2]),
- new Set.from([5]),
+ var innerSet = new Set.of([3, 7]);
+ set = new UnionSet.from([
+ new Set.of([1, 2]),
+ new Set.of([5]),
innerSet
]);
@@ -178,16 +178,16 @@
group("after the outer set was modified", () {
var set;
setUp(() {
- var innerSet = new Set.from([6]);
- var outerSet = new Set<Set<int>>.from([
- new Set.from([1, 2]),
- new Set.from([5]),
+ var innerSet = new Set.of([6]);
+ var outerSet = new Set.of([
+ new Set.of([1, 2]),
+ new Set.of([5]),
innerSet
]);
set = new UnionSet<int>(outerSet);
outerSet.remove(innerSet);
- outerSet.add(new Set.from([3, 4]));
+ outerSet.add(new Set.of([3, 4]));
});
test("length returns the total length", () {
diff --git a/packages/collection/test/unmodifiable_collection_test.dart b/packages/collection/test/unmodifiable_collection_test.dart
index a6705f6..c7b539f 100644
--- a/packages/collection/test/unmodifiable_collection_test.dart
+++ b/packages/collection/test/unmodifiable_collection_test.dart
@@ -37,13 +37,13 @@
testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "empty");
aSet = new Set();
testUnmodifiableSet(aSet, const UnmodifiableSetView.empty(), "const empty");
- aSet = new Set.from([42]);
+ aSet = new Set.of([42]);
testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "single-42");
- aSet = new Set.from([7]);
+ aSet = new Set.of([7]);
testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "single!42");
- aSet = new Set.from([1, 42, 10]);
+ aSet = new Set.of([1, 42, 10]);
testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "three-42");
- aSet = new Set.from([1, 7, 10]);
+ aSet = new Set.of([1, 7, 10]);
testUnmodifiableSet(aSet, new UnmodifiableSetView(aSet), "three!42");
}
@@ -311,8 +311,8 @@
});
}
-void testNoWriteList(List original, List wrapped, String name) {
- List copy = new List.from(original);
+void testNoWriteList(List<int> original, List<int> wrapped, String name) {
+ var copy = new List.of(original);
testThrows(name, thunk) {
test(name, () {
@@ -345,7 +345,7 @@
}
void testWriteList(List<int> original, List wrapped, String name) {
- var copy = new List<int>.from(original);
+ var copy = new List.of(original);
test("$name - []=", () {
if (original.isNotEmpty) {
@@ -361,7 +361,7 @@
});
test("$name - sort", () {
- List sortCopy = new List.from(original);
+ List sortCopy = new List.of(original);
sortCopy.sort();
wrapped.sort();
expect(original, orderedEquals(sortCopy));
@@ -391,8 +391,9 @@
});
}
-void testNoChangeLengthList(List original, List wrapped, String name) {
- List copy = new List.from(original);
+void testNoChangeLengthList(
+ List<int> original, List<int> wrapped, String name) {
+ var copy = new List.of(original);
void testThrows(String name, thunk) {
test(name, () {
@@ -455,8 +456,8 @@
});
}
-void testReadSet(Set original, Set wrapped, String name) {
- Set copy = new Set.from(original);
+void testReadSet(Set<int> original, Set<int> wrapped, String name) {
+ var copy = new Set.of(original);
test("$name - containsAll", () {
expect(wrapped.containsAll(copy), isTrue);
@@ -468,27 +469,27 @@
test("$name - intersection", () {
expect(wrapped.intersection(new Set()), isEmpty);
expect(wrapped.intersection(copy), unorderedEquals(original));
- expect(wrapped.intersection(new Set.from([42])),
- new Set.from(original.contains(42) ? [42] : []));
+ expect(wrapped.intersection(new Set.of([42])),
+ new Set.of(original.contains(42) ? [42] : []));
});
test("$name - union", () {
expect(wrapped.union(new Set()), unorderedEquals(original));
expect(wrapped.union(copy), unorderedEquals(original));
- expect(wrapped.union(new Set.from([42])),
- equals(original.union(new Set.from([42]))));
+ expect(wrapped.union(new Set.of([42])),
+ equals(original.union(new Set.of([42]))));
});
test("$name - difference", () {
expect(wrapped.difference(new Set()), unorderedEquals(original));
expect(wrapped.difference(copy), isEmpty);
- expect(wrapped.difference(new Set.from([42])),
- equals(original.difference(new Set.from([42]))));
+ expect(wrapped.difference(new Set.of([42])),
+ equals(original.difference(new Set.of([42]))));
});
}
-void testNoChangeSet(Set original, Set wrapped, String name) {
- List originalElements = original.toList();
+void testNoChangeSet(Set<int> original, Set<int> wrapped, String name) {
+ var originalElements = original.toList();
testThrows(name, thunk) {
test(name, () {
@@ -588,8 +589,8 @@
});
}
-testNoChangeMap(Map original, Map wrapped, String name) {
- Map copy = new Map.from(original);
+testNoChangeMap(Map<int, int> original, Map<int, int> wrapped, String name) {
+ var copy = new Map.of(original);
testThrows(name, thunk) {
test(name, () {
diff --git a/packages/collection/test/utils.dart b/packages/collection/test/utils.dart
index d8ab082..73926a3 100644
--- a/packages/collection/test/utils.dart
+++ b/packages/collection/test/utils.dart
@@ -5,3 +5,13 @@
import "package:test/test.dart";
final Matcher throwsCastError = throwsA(new isInstanceOf<CastError>());
+
+/// A hack to determine whether we are running in a Dart 2 runtime.
+final bool isDart2 = _isTypeArgString('');
+bool _isTypeArgString<T>(T arg) {
+ try {
+ return T == String;
+ } catch (_) {
+ return false;
+ }
+}
diff --git a/packages/collection/test/wrapper_test.dart b/packages/collection/test/wrapper_test.dart
index 95e6f36..bb8f90f 100644
--- a/packages/collection/test/wrapper_test.dart
+++ b/packages/collection/test/wrapper_test.dart
@@ -25,250 +25,237 @@
/// Utility class to record a member access and a member access on a wrapped
/// object, and compare them for equality.
+///
+/// Use as `(expector..someAccess()).equals.someAccess();`.
+/// Alle the intercepted member accesses returns `null`.
abstract class Expector {
- getWrappedObject(action(Invocation i));
- // Hack to test assignment ([]=) because it doesn't return the result
- // of the member call. Instead use (expect..[4]=5).equal[4]=5 where
- // you would normally use expect[4].equals[4] for non-assignments.
+ wrappedChecker(Invocation i);
+ // After calling any member on the Expector, equals is an object that expects
+ // the *same* invocation on the wrapped object.
var equals;
- noSuchMethod(Invocation m) => new _Equals(equals = getWrappedObject((m2) {
- testInvocations(m, m2);
- }));
+ noSuchMethod(Invocation i) {
+ equals = wrappedChecker(i);
+ return null;
+ }
- // dartanalyzer complains if this method is named `toString()`, since, if it
- // truly overrides Object's `toString()`, it should return a String.
- asString() => new _Equals(equals = getWrappedObject((m2) {
- testInvocations(TO_STRING_INVOCATION, m2);
- }));
+ toString() {
+ // Cannot return an _Equals object since toString must return a String.
+ // Just set equals and return a string.
+ equals = wrappedChecker(toStringInvocation);
+ return "";
+ }
}
-// An object with a field called "equals", only introduced into the
-// flow to allow writing expect.xxx.equals.xxx.
-class _Equals {
- final equals;
- _Equals(this.equals);
+// Parameterization of noSuchMethod. Calls [_action] on every
+// member invocation.
+class InvocationChecker {
+ Invocation _expected;
+ InvocationChecker(this._expected);
+ noSuchMethod(Invocation actual) {
+ testInvocations(_expected, actual);
+ return null;
+ }
+
+ toString() {
+ testInvocations(_expected, toStringInvocation);
+ return "";
+ }
+ // Could also handle runtimeType, hashCode and == the same way as
+ // toString, but we are not testing them since collections generally
+ // don't override those and so the wrappers don't forward those.
}
-class SyntheticInvocation implements Invocation {
- static const int METHOD = 0x00;
- static const int GETTER = 0x01;
- static const int SETTER = 0x02;
- final Symbol memberName;
- final List positionalArguments;
- final Map<Symbol, dynamic> namedArguments;
- final int _type;
- const SyntheticInvocation(this.memberName, this.positionalArguments,
- this.namedArguments, this._type);
+final toStringInvocation = new Invocation.method(#toString, const []);
- List<Type> get typeArguments => const <Type>[];
-
- bool get isMethod => _type == METHOD;
-
- bool get isGetter => _type == GETTER;
-
- bool get isSetter => _type == SETTER;
-
- bool get isAccessor => isGetter || isSetter;
+// InvocationCheckers with types Queue, Set, List or Iterable to allow them as
+// argument to DelegatingIterable/Set/List/Queue.
+class IterableInvocationChecker<T> extends InvocationChecker
+ implements Iterable<T> {
+ IterableInvocationChecker(Invocation expected) : super(expected);
}
-// Parameterization of noSuchMethod.
-class NSM {
- Function _action;
- NSM(this._action);
- noSuchMethod(Invocation i) => _action(i);
+class ListInvocationChecker<T> extends InvocationChecker implements List<T> {
+ ListInvocationChecker(Invocation expected) : super(expected);
}
-const TO_STRING_INVOCATION = const SyntheticInvocation(
- #toString, const [], const {}, SyntheticInvocation.METHOD);
+class SetInvocationChecker<T> extends InvocationChecker implements Set<T> {
+ SetInvocationChecker(Invocation expected) : super(expected);
+}
-// LikeNSM, but has types Iterable, Set and List to allow it as
-// argument to DelegatingIterable/Set/List.
-class IterableNSM extends NSM implements Iterable, Set, List, Queue {
- IterableNSM(action(Invocation i)) : super(action);
- toString() => super.noSuchMethod(TO_STRING_INVOCATION) as String;
+class QueueInvocationChecker<T> extends InvocationChecker implements Queue<T> {
+ QueueInvocationChecker(Invocation expected) : super(expected);
+}
- Null cast<T>();
- Null retype<T>();
+class MapInvocationChecker<K, V> extends InvocationChecker
+ implements Map<K, V> {
+ MapInvocationChecker(Invocation expected) : super(expected);
}
// Expector that wraps in DelegatingIterable.
-class IterableExpector extends Expector {
- getWrappedObject(void action(Invocation i)) {
- return new DelegatingIterable(new IterableNSM(action));
- }
+class IterableExpector<T> extends Expector implements Iterable<T> {
+ wrappedChecker(Invocation i) =>
+ new DelegatingIterable<T>(new IterableInvocationChecker<T>(i));
}
// Expector that wraps in DelegatingList.
-class ListExpector extends Expector {
- getWrappedObject(void action(Invocation i)) {
- return new DelegatingList(new IterableNSM(action));
- }
+class ListExpector<T> extends Expector implements List<T> {
+ wrappedChecker(Invocation i) =>
+ new DelegatingList<T>(new ListInvocationChecker<T>(i));
}
// Expector that wraps in DelegatingSet.
-class SetExpector extends Expector {
- getWrappedObject(void action(Invocation i)) {
- return new DelegatingSet(new IterableNSM(action));
- }
+class SetExpector<T> extends Expector implements Set<T> {
+ wrappedChecker(Invocation i) =>
+ new DelegatingSet<T>(new SetInvocationChecker<T>(i));
}
// Expector that wraps in DelegatingSet.
-class QueueExpector extends Expector {
- getWrappedObject(void action(Invocation i)) {
- return new DelegatingQueue(new IterableNSM(action));
- }
-}
-
-// Like NSM but implements Map to allow as argument for DelegatingMap.
-class MapNSM extends NSM implements Map {
- MapNSM(action(Invocation i)) : super(action);
- toString() => super.noSuchMethod(TO_STRING_INVOCATION) as String;
+class QueueExpector<T> extends Expector implements Queue<T> {
+ wrappedChecker(Invocation i) =>
+ new DelegatingQueue<T>(new QueueInvocationChecker<T>(i));
}
// Expector that wraps in DelegatingMap.
-class MapExpector extends Expector {
- getWrappedObject(void action(Invocation i)) {
- return new DelegatingMap(new MapNSM(action));
- }
+class MapExpector<K, V> extends Expector implements Map<K, V> {
+ wrappedChecker(Invocation i) =>
+ new DelegatingMap<K, V>(new MapInvocationChecker<K, V>(i));
}
// Utility values to use as arguments in calls.
-func0() {}
-func1(x) {}
-func2(x, y) {}
+Null func0() => null;
+Null func1(Object x) => null;
+Null func2(Object x, Object y) => null;
var val = new Object();
void main() {
testIterable(var expect) {
- expect.any(func1).equals.any(func1);
- expect.contains(val).equals.contains(val);
- expect.elementAt(0).equals.elementAt(0);
- expect.every(func1).equals.every(func1);
- expect.expand(func1).equals.expand(func1);
- expect.first.equals.first;
+ (expect..any(func1)).equals.any(func1);
+ (expect..contains(val)).equals.contains(val);
+ (expect..elementAt(0)).equals.elementAt(0);
+ (expect..every(func1)).equals.every(func1);
+ (expect..expand(func1)).equals.expand(func1);
+ (expect..first).equals.first;
// Default values of the Iterable interface will be added in the
// second call to firstWhere, so we must record them in our
// expectation (which doesn't have the interface implemented or
// its default values).
- expect.firstWhere(func1, orElse: null).equals.firstWhere(func1);
- expect
- .firstWhere(func1, orElse: func0)
+ (expect..firstWhere(func1, orElse: null)).equals.firstWhere(func1);
+ (expect..firstWhere(func1, orElse: func0))
.equals
.firstWhere(func1, orElse: func0);
- expect.fold(null, func2).equals.fold(null, func2);
- expect.forEach(func1).equals.forEach(func1);
- expect.isEmpty.equals.isEmpty;
- expect.isNotEmpty.equals.isNotEmpty;
- expect.iterator.equals.iterator;
- expect.join('').equals.join();
- expect.join("X").equals.join("X");
- expect.last.equals.last;
- expect.lastWhere(func1, orElse: null).equals.lastWhere(func1);
- expect
- .lastWhere(func1, orElse: func0)
+ (expect..fold(null, func2)).equals.fold(null, func2);
+ (expect..forEach(func1)).equals.forEach(func1);
+ (expect..isEmpty).equals.isEmpty;
+ (expect..isNotEmpty).equals.isNotEmpty;
+ (expect..iterator).equals.iterator;
+ (expect..join('')).equals.join();
+ (expect..join("X")).equals.join("X");
+ (expect..last).equals.last;
+ (expect..lastWhere(func1, orElse: null)).equals.lastWhere(func1);
+ (expect..lastWhere(func1, orElse: func0))
.equals
.lastWhere(func1, orElse: func0);
- expect.length.equals.length;
- expect.map(func1).equals.map(func1);
- expect.reduce(func2).equals.reduce(func2);
- expect.single.equals.single;
- expect.singleWhere(func1).equals.singleWhere(func1);
- expect.skip(5).equals.skip(5);
- expect.skipWhile(func1).equals.skipWhile(func1);
- expect.take(5).equals.take(5);
- expect.takeWhile(func1).equals.takeWhile(func1);
- expect.toList(growable: true).equals.toList();
- expect.toList(growable: true).equals.toList(growable: true);
- expect.toList(growable: false).equals.toList(growable: false);
- expect.toSet().equals.toSet();
- expect.asString().equals.toString();
- expect.where(func1).equals.where(func1);
+ (expect..length).equals.length;
+ (expect..map(func1)).equals.map(func1);
+ (expect..reduce(func2)).equals.reduce(func2);
+ (expect..single).equals.single;
+ (expect..singleWhere(func1, orElse: null)).equals.singleWhere(func1);
+ (expect..skip(5)).equals.skip(5);
+ (expect..skipWhile(func1)).equals.skipWhile(func1);
+ (expect..take(5)).equals.take(5);
+ (expect..takeWhile(func1)).equals.takeWhile(func1);
+ (expect..toList(growable: true)).equals.toList();
+ (expect..toList(growable: true)).equals.toList(growable: true);
+ (expect..toList(growable: false)).equals.toList(growable: false);
+ (expect..toSet()).equals.toSet();
+ (expect..toString()).equals.toString();
+ (expect..where(func1)).equals.where(func1);
}
void testList(var expect) {
testIterable(expect);
- expect[4].equals[4];
+ (expect..[4]).equals[4];
(expect..[4] = 5).equals[4] = 5;
- expect.add(val).equals.add(val);
- expect.addAll([val]).equals.addAll([val]);
- expect.asMap().equals.asMap();
- expect.clear().equals.clear();
- expect.fillRange(4, 5, null).equals.fillRange(4, 5);
- expect.fillRange(4, 5, val).equals.fillRange(4, 5, val);
- expect.getRange(4, 5).equals.getRange(4, 5);
- expect.indexOf(val, 0).equals.indexOf(val);
- expect.indexOf(val, 4).equals.indexOf(val, 4);
- expect.insert(4, val).equals.insert(4, val);
- expect.insertAll(4, [val]).equals.insertAll(4, [val]);
- expect.lastIndexOf(val, null).equals.lastIndexOf(val);
- expect.lastIndexOf(val, 4).equals.lastIndexOf(val, 4);
+ (expect..add(val)).equals.add(val);
+ (expect..addAll([val])).equals.addAll([val]);
+ (expect..asMap()).equals.asMap();
+ (expect..clear()).equals.clear();
+ (expect..fillRange(4, 5, null)).equals.fillRange(4, 5);
+ (expect..fillRange(4, 5, val)).equals.fillRange(4, 5, val);
+ (expect..getRange(4, 5)).equals.getRange(4, 5);
+ (expect..indexOf(val, 0)).equals.indexOf(val);
+ (expect..indexOf(val, 4)).equals.indexOf(val, 4);
+ (expect..insert(4, val)).equals.insert(4, val);
+ (expect..insertAll(4, [val])).equals.insertAll(4, [val]);
+ (expect..lastIndexOf(val, null)).equals.lastIndexOf(val);
+ (expect..lastIndexOf(val, 4)).equals.lastIndexOf(val, 4);
(expect..length = 4).equals.length = 4;
- expect.remove(val).equals.remove(val);
- expect.removeAt(4).equals.removeAt(4);
- expect.removeLast().equals.removeLast();
- expect.removeRange(4, 5).equals.removeRange(4, 5);
- expect.removeWhere(func1).equals.removeWhere(func1);
- expect.replaceRange(4, 5, [val]).equals.replaceRange(4, 5, [val]);
- expect.retainWhere(func1).equals.retainWhere(func1);
- expect.reversed.equals.reversed;
- expect.setAll(4, [val]).equals.setAll(4, [val]);
- expect.setRange(4, 5, [val], 0).equals.setRange(4, 5, [val]);
- expect.setRange(4, 5, [val], 3).equals.setRange(4, 5, [val], 3);
- expect.sort(null).equals.sort();
- expect.sort(func2).equals.sort(func2);
- expect.sublist(4, null).equals.sublist(4);
- expect.sublist(4, 5).equals.sublist(4, 5);
+ (expect..remove(val)).equals.remove(val);
+ (expect..removeAt(4)).equals.removeAt(4);
+ (expect..removeLast()).equals.removeLast();
+ (expect..removeRange(4, 5)).equals.removeRange(4, 5);
+ (expect..removeWhere(func1)).equals.removeWhere(func1);
+ (expect..replaceRange(4, 5, [val])).equals.replaceRange(4, 5, [val]);
+ (expect..retainWhere(func1)).equals.retainWhere(func1);
+ (expect..reversed).equals.reversed;
+ (expect..setAll(4, [val])).equals.setAll(4, [val]);
+ (expect..setRange(4, 5, [val], 0)).equals.setRange(4, 5, [val]);
+ (expect..setRange(4, 5, [val], 3)).equals.setRange(4, 5, [val], 3);
+ (expect..sort(null)).equals.sort();
+ (expect..sort(func2)).equals.sort(func2);
+ (expect..sublist(4, null)).equals.sublist(4);
+ (expect..sublist(4, 5)).equals.sublist(4, 5);
}
void testSet(var expect) {
testIterable(expect);
Set set = new Set();
- expect.add(val).equals.add(val);
- expect.addAll([val]).equals.addAll([val]);
- expect.clear().equals.clear();
- expect.containsAll([val]).equals.containsAll([val]);
- expect.difference(set).equals.difference(set);
- expect.intersection(set).equals.intersection(set);
- expect.remove(val).equals.remove(val);
- expect.removeAll([val]).equals.removeAll([val]);
- expect.removeWhere(func1).equals.removeWhere(func1);
- expect.retainAll([val]).equals.retainAll([val]);
- expect.retainWhere(func1).equals.retainWhere(func1);
- expect.union(set).equals.union(set);
+ (expect..add(val)).equals.add(val);
+ (expect..addAll([val])).equals.addAll([val]);
+ (expect..clear()).equals.clear();
+ (expect..containsAll([val])).equals.containsAll([val]);
+ (expect..difference(set)).equals.difference(set);
+ (expect..intersection(set)).equals.intersection(set);
+ (expect..remove(val)).equals.remove(val);
+ (expect..removeAll([val])).equals.removeAll([val]);
+ (expect..removeWhere(func1)).equals.removeWhere(func1);
+ (expect..retainAll([val])).equals.retainAll([val]);
+ (expect..retainWhere(func1)).equals.retainWhere(func1);
+ (expect..union(set)).equals.union(set);
}
void testQueue(var expect) {
testIterable(expect);
- expect.add(val).equals.add(val);
- expect.addAll([val]).equals.addAll([val]);
- expect.addFirst(val).equals.addFirst(val);
- expect.addLast(val).equals.addLast(val);
- expect.clear().equals.clear();
- expect.remove(val).equals.remove(val);
- expect.removeFirst().equals.removeFirst();
- expect.removeLast().equals.removeLast();
+ (expect..add(val)).equals.add(val);
+ (expect..addAll([val])).equals.addAll([val]);
+ (expect..addFirst(val)).equals.addFirst(val);
+ (expect..addLast(val)).equals.addLast(val);
+ (expect..clear()).equals.clear();
+ (expect..remove(val)).equals.remove(val);
+ (expect..removeFirst()).equals.removeFirst();
+ (expect..removeLast()).equals.removeLast();
}
void testMap(var expect) {
Map map = new Map();
- expect[val].equals[val];
+ (expect..[val]).equals[val];
(expect..[val] = val).equals[val] = val;
- expect.addAll(map).equals.addAll(map);
- expect.clear().equals.clear();
- expect.containsKey(val).equals.containsKey(val);
- expect.containsValue(val).equals.containsValue(val);
- expect.forEach(func2).equals.forEach(func2);
- expect.isEmpty.equals.isEmpty;
- expect.isNotEmpty.equals.isNotEmpty;
- expect.keys.equals.keys;
- expect.length.equals.length;
- expect.putIfAbsent(val, func0).equals.putIfAbsent(val, func0);
- expect.remove(val).equals.remove(val);
- expect.values.equals.values;
- expect.asString().equals.toString();
+ (expect..addAll(map)).equals.addAll(map);
+ (expect..clear()).equals.clear();
+ (expect..containsKey(val)).equals.containsKey(val);
+ (expect..containsValue(val)).equals.containsValue(val);
+ (expect..forEach(func2)).equals.forEach(func2);
+ (expect..isEmpty).equals.isEmpty;
+ (expect..isNotEmpty).equals.isNotEmpty;
+ (expect..keys).equals.keys;
+ (expect..length).equals.length;
+ (expect..putIfAbsent(val, func0)).equals.putIfAbsent(val, func0);
+ (expect..remove(val)).equals.remove(val);
+ (expect..values).equals.values;
+ (expect..toString()).equals.toString();
}
// Runs tests of Set behavior.
@@ -276,7 +263,7 @@
// [setUpSet] should return a set with two elements: "foo" and "bar".
void testTwoElementSet(Set<String> setUpSet()) {
group("with two elements", () {
- var set;
+ Set<String> set;
setUp(() => set = setUpSet());
test(".any", () {
diff --git a/packages/collection/tool/travis.sh b/packages/collection/tool/travis.sh
new file mode 100755
index 0000000..0e584b9
--- /dev/null
+++ b/packages/collection/tool/travis.sh
@@ -0,0 +1,65 @@
+# Copyright 2018 the Dart project authors.
+#
+# 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.
+
+#!/bin/bash
+
+if [ "$#" == "0" ]; then
+ echo -e '\033[31mAt least one task argument must be provided!\033[0m'
+ exit 1
+fi
+
+EXIT_CODE=0
+
+while (( "$#" )); do
+ TASK=$1
+ case $TASK in
+ dartfmt) echo
+ echo -e '\033[1mTASK: dartfmt\033[22m'
+ echo -e 'dartfmt -n --set-exit-if-changed .'
+ dartfmt -n --set-exit-if-changed . || EXIT_CODE=$?
+ ;;
+ dartanalyzer) echo
+ echo -e '\033[1mTASK: dartanalyzer\033[22m'
+ echo -e 'dartanalyzer --fatal-warnings .'
+ dartanalyzer --fatal-warnings . || EXIT_CODE=$?
+ ;;
+ vm_test) echo
+ echo -e '\033[1mTASK: vm_test\033[22m'
+ echo -e 'pub run test -P travis -p vm -x requires-dart2'
+ pub run test -p vm || EXIT_CODE=$?
+ ;;
+ dartdevc_build) echo
+ echo -e '\033[1mTASK: build\033[22m'
+ echo -e 'pub run build_runner build --fail-on-severe'
+ pub run build_runner build --fail-on-severe || EXIT_CODE=$?
+ ;;
+ dartdevc_test) echo
+ echo -e '\033[1mTASK: dartdevc_test\033[22m'
+ echo -e 'pub run build_runner test -- -P travis -p chrome'
+ pub run build_runner test -- -p chrome || EXIT_CODE=$?
+ ;;
+ dart2js_test) echo
+ echo -e '\033[1mTASK: dart2js_test\033[22m'
+ echo -e 'pub run test -P travis -p chrome -x requires-dart2'
+ pub run test -p chrome || EXIT_CODE=$?
+ ;;
+ *) echo -e "\033[31mNot expecting TASK '${TASK}'. Error!\033[0m"
+ EXIT_CODE=1
+ ;;
+ esac
+
+ shift
+done
+
+exit $EXIT_CODE
diff --git a/packages/dart_internal/LICENSE b/packages/dart_internal/LICENSE
new file mode 100644
index 0000000..389ce98
--- /dev/null
+++ b/packages/dart_internal/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2017, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/dart_internal/README.md b/packages/dart_internal/README.md
new file mode 100644
index 0000000..3ce15a1
--- /dev/null
+++ b/packages/dart_internal/README.md
@@ -0,0 +1,15 @@
+**Scary warning: This package is experimental and may deprecated in a future
+version of Dart.**
+
+This package is not intended for wide use. It provides a temporary API to
+solve the problem: "Given an object some generic type A, how do I construct an
+instance of generic type B with the same type argument(s)?"
+
+This is necessary in a few rare places in order to migrate existing code to
+Dart 2's stronger type system. Eventually, the hope is to have direct
+language support for solving this problem but we don't have time to get that
+into 2.0, so this package is provided as a temporary workaround.
+
+We will very likely remove support for this in a later version of Dart. Please
+avoid using this if you can. If you feel you *do* need to use it, please reach
+out to @munificent or @leafpetersen and let us know.
diff --git a/packages/dart_internal/analysis_options.yaml b/packages/dart_internal/analysis_options.yaml
new file mode 100644
index 0000000..1a46de2
--- /dev/null
+++ b/packages/dart_internal/analysis_options.yaml
@@ -0,0 +1,3 @@
+analyzer:
+ strong-mode:
+ implicit-casts: false
diff --git a/packages/dart_internal/lib/extract_type_arguments.dart b/packages/dart_internal/lib/extract_type_arguments.dart
new file mode 100644
index 0000000..218d075
--- /dev/null
+++ b/packages/dart_internal/lib/extract_type_arguments.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// The actual functionality exposed by this package is implemented in
+// "dart:_internal" since it is specific to each platform's runtime
+// implementation. This package exists as a shell to expose that internal API
+// to outside code.
+//
+// Only this exact special file is allowed to import "dart:_internal" without
+// causing a compile error.
+// ignore: import_internal_library
+import 'dart:_internal' as internal;
+
+/// Given an [Iterable], invokes [extract], passing the [iterable]'s type
+/// argument as the type argument to the generic function.
+///
+/// Example:
+///
+/// ```dart
+/// Object iterable = <int>[];
+/// print(extractIterableTypeArgument(iterable, <T>() => new Set<T>());
+/// // Prints "Instance of 'Set<int>'".
+/// ```
+Object extractIterableTypeArgument(
+ Iterable iterable, Object Function<T>() extract) =>
+ internal.extractTypeArguments<Iterable>(iterable, extract);
+
+/// Given a [Map], invokes [extract], passing the [map]'s key and value type
+/// arguments as the type arguments to the generic function.
+///
+/// Example:
+///
+/// ```dart
+/// class Two<A, B> {}
+///
+/// main() {
+/// Object map = <String, int>{};
+/// print(extractMapTypeArguments(map, <K, V>() => new Two<K, V>());
+/// // Prints "Instance of 'Two<String, int>'".
+/// }
+/// ```
+Object extractMapTypeArguments(Map map, Object Function<K, V>() extract) =>
+ internal.extractTypeArguments<Map>(map, extract);
diff --git a/packages/dart_internal/pubspec.yaml b/packages/dart_internal/pubspec.yaml
new file mode 100644
index 0000000..945aaa1
--- /dev/null
+++ b/packages/dart_internal/pubspec.yaml
@@ -0,0 +1,19 @@
+name: dart_internal
+version: 0.1.1
+author: "Dart Team <misc@dartlang.org>"
+homepage: http://www.dartlang.org
+description: >
+ This package is not intended for wide use. It provides a temporary API to
+ solve the problem: "Given an object some generic type A, how do I construct an
+ instance of generic type B with the same type argument(s)?"
+
+ This is necessary in a few rare places in order to migrate existing code to
+ Dart 2's stronger type system. Eventually, the hope is to have direct
+ language support for solving this problem but we don't have time to get that
+ into 2.0, so this package is provided as a temporary workaround.
+
+ We will very likely remove support for this in a later version of Dart.
+environment:
+ # Restrict the upper bound so that we can remove support for this in a later
+ # version of the SDK without it being a breaking change.
+ sdk: ">=2.0.0-dev.12.0 <2.1.0"
diff --git a/packages/matcher/.gitignore b/packages/matcher/.gitignore
index 7dbf035..ab3cb76 100644
--- a/packages/matcher/.gitignore
+++ b/packages/matcher/.gitignore
@@ -1,5 +1,6 @@
# Don’t commit the following directories created by pub.
.buildlog
+.dart_tool/
.pub/
build/
packages
@@ -12,4 +13,4 @@
*.js.map
# Include when developing application packages.
-pubspec.lock
\ No newline at end of file
+pubspec.lock
diff --git a/packages/matcher/.travis.yml b/packages/matcher/.travis.yml
index aa48444..3d7f554 100644
--- a/packages/matcher/.travis.yml
+++ b/packages/matcher/.travis.yml
@@ -1,7 +1,6 @@
language: dart
dart:
- - stable
- dev
dart_task:
@@ -9,11 +8,7 @@
xvfb: false
- test: -p firefox
- dartanalyzer
-
-matrix:
- include:
- - dart: stable
- dart_task: dartfmt
+ - dartfmt
# Only building master means that we don't run two builds for each pull request.
branches:
diff --git a/packages/matcher/CHANGELOG.md b/packages/matcher/CHANGELOG.md
index a5ebcfd..04e3ba0 100644
--- a/packages/matcher/CHANGELOG.md
+++ b/packages/matcher/CHANGELOG.md
@@ -1,3 +1,35 @@
+## 0.12.3+1
+
+- Set max SDK version to <3.0.0, and adjusted other dependencies.
+
+## 0.12.3
+
+- Many improvements to `TypeMatcher`
+ - Can now be used directly as `const TypeMatcher<MyType>()`.
+ - Added a type parameter to specify the target `Type`.
+ - Made the `name` constructor parameter optional and marked it deprecated.
+ It's redundant to the type parameter.
+ - Migrated all `isType` matchers to `TypeMatcher`.
+ - Added a `having` function that allows chained validations of specific
+ features of the target type.
+
+ ```dart
+ /// Validates that the object is a [RangeError] with a message containing
+ /// the string 'details' and `start` and `end` properties that are `null`.
+ final _rangeMatcher = isRangeError
+ .having((e) => e.message, 'message', contains('details'))
+ .having((e) => e.start, 'start', isNull)
+ .having((e) => e.end, 'end', isNull);
+ ```
+
+- Deprecated the `isInstanceOf` class. Use `TypeMatcher` instead.
+
+- Improved the output of `Matcher` instances that fail due to type errors.
+
+## 0.12.2+1
+
+- Updated SDK version to 2.0.0-dev.17.0
+
## 0.12.2
* Fixed `unorderedMatches` in cases where the matchers may match more than one
diff --git a/packages/matcher/analysis_options.yaml b/packages/matcher/analysis_options.yaml
index a10d4c5..bcff04b 100644
--- a/packages/matcher/analysis_options.yaml
+++ b/packages/matcher/analysis_options.yaml
@@ -1,2 +1,77 @@
analyzer:
- strong-mode: true
+ errors:
+ dead_code: error
+ override_on_non_overriding_method: error
+ unused_element: error
+ unused_import: error
+ unused_local_variable: error
+linter:
+ rules:
+ - always_declare_return_types
+ #- annotate_overrides
+ - avoid_empty_else
+ - avoid_function_literals_in_foreach_calls
+ - avoid_init_to_null
+ - avoid_null_checks_in_equality_operators
+ - avoid_renaming_method_parameters
+ - avoid_return_types_on_setters
+ - avoid_returning_null
+ - avoid_types_as_parameter_names
+ - avoid_unused_constructor_parameters
+ - await_only_futures
+ - camel_case_types
+ - cancel_subscriptions
+ #- cascade_invocations
+ - comment_references
+ - constant_identifier_names
+ - control_flow_in_finally
+ - directives_ordering
+ - empty_catches
+ - empty_constructor_bodies
+ - empty_statements
+ - hash_and_equals
+ - implementation_imports
+ - invariant_booleans
+ - iterable_contains_unrelated_type
+ - library_names
+ - library_prefixes
+ - list_remove_unrelated_type
+ - literal_only_boolean_expressions
+ - no_adjacent_strings_in_list
+ - no_duplicate_case_values
+ - non_constant_identifier_names
+ - omit_local_variable_types
+ - only_throw_errors
+ - overridden_fields
+ #- package_api_docs
+ - package_names
+ - package_prefixed_library_names
+ - prefer_adjacent_string_concatenation
+ - prefer_collection_literals
+ - prefer_conditional_assignment
+ - prefer_const_constructors
+ - prefer_contains
+ - prefer_equal_for_default_values
+ - prefer_final_fields
+ - prefer_initializing_formals
+ #- prefer_interpolation_to_compose_strings
+ - prefer_is_empty
+ - prefer_is_not_empty
+ #- prefer_single_quotes
+ - prefer_typing_uninitialized_variables
+ - recursive_getters
+ - slash_for_doc_comments
+ - super_goes_last
+ - test_types_in_equals
+ - throw_in_finally
+ - type_init_formals
+ - unawaited_futures
+ - unnecessary_brace_in_string_interps
+ - unnecessary_getters_setters
+ - unnecessary_lambdas
+ - unnecessary_null_aware_assignments
+ - unnecessary_statements
+ - unnecessary_this
+ - unrelated_type_equality_checks
+ - use_rethrow_when_possible
+ - valid_regexps
diff --git a/packages/matcher/lib/matcher.dart b/packages/matcher/lib/matcher.dart
index b4b9d22..72918aa 100644
--- a/packages/matcher/lib/matcher.dart
+++ b/packages/matcher/lib/matcher.dart
@@ -4,7 +4,9 @@
/// Support for specifying test expectations, such as for unit tests.
export 'src/core_matchers.dart';
+export 'src/custom_matcher.dart';
export 'src/description.dart';
+export 'src/equals_matcher.dart';
export 'src/error_matchers.dart';
export 'src/interfaces.dart';
export 'src/iterable_matchers.dart';
@@ -13,4 +15,5 @@
export 'src/operator_matchers.dart';
export 'src/order_matchers.dart';
export 'src/string_matchers.dart';
+export 'src/type_matcher.dart';
export 'src/util.dart';
diff --git a/packages/matcher/lib/mirror_matchers.dart b/packages/matcher/lib/mirror_matchers.dart
index 0ce90da..eb2225a 100644
--- a/packages/matcher/lib/mirror_matchers.dart
+++ b/packages/matcher/lib/mirror_matchers.dart
@@ -29,8 +29,8 @@
addStateInfo(matchState, {'reason': 'has no property named "$_name"'});
return false;
}
- bool isInstanceField = candidate is VariableMirror && !candidate.isStatic;
- bool isInstanceGetter =
+ var isInstanceField = candidate is VariableMirror && !candidate.isStatic;
+ var isInstanceGetter =
candidate is MethodMirror && candidate.isGetter && !candidate.isStatic;
if (!(isInstanceField || isInstanceGetter)) {
addStateInfo(matchState, {
@@ -60,14 +60,14 @@
item, Description mismatchDescription, Map matchState, bool verbose) {
var reason = matchState == null ? null : matchState['reason'];
if (reason != null) {
- mismatchDescription.add(reason);
+ mismatchDescription.add(reason as String);
} else {
mismatchDescription
.add('has property "$_name" with value ')
.addDescriptionOf(matchState['value']);
var innerDescription = new StringDescription();
- _matcher.describeMismatch(
- matchState['value'], innerDescription, matchState['state'], verbose);
+ _matcher.describeMismatch(matchState['value'], innerDescription,
+ matchState['state'] as Map, verbose);
if (innerDescription.length > 0) {
mismatchDescription.add(' which ').add(innerDescription.toString());
}
diff --git a/packages/matcher/lib/src/core_matchers.dart b/packages/matcher/lib/src/core_matchers.dart
index e8fdb11..73570e3 100644
--- a/packages/matcher/lib/src/core_matchers.dart
+++ b/packages/matcher/lib/src/core_matchers.dart
@@ -2,10 +2,9 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-import 'package:stack_trace/stack_trace.dart';
-
-import 'description.dart';
+import 'feature_matcher.dart';
import 'interfaces.dart';
+import 'type_matcher.dart';
import 'util.dart';
/// Returns a matcher that matches the isEmpty property.
@@ -72,15 +71,17 @@
/// A matcher that matches any non-NaN value.
const Matcher isNotNaN = const _IsNotNaN();
-class _IsNaN extends Matcher {
+class _IsNaN extends FeatureMatcher<num> {
const _IsNaN();
- bool matches(item, Map matchState) => double.NAN.compareTo(item) == 0;
+ bool typedMatches(num item, Map matchState) =>
+ double.nan.compareTo(item) == 0;
Description describe(Description description) => description.add('NaN');
}
-class _IsNotNaN extends Matcher {
+class _IsNotNaN extends FeatureMatcher<num> {
const _IsNotNaN();
- bool matches(item, Map matchState) => double.NAN.compareTo(item) != 0;
+ bool typedMatches(num item, Map matchState) =>
+ double.nan.compareTo(item) != 0;
Description describe(Description description) => description.add('not NaN');
}
@@ -89,7 +90,7 @@
Matcher same(expected) => new _IsSameAs(expected);
class _IsSameAs extends Matcher {
- final _expected;
+ final Object _expected;
const _IsSameAs(this._expected);
bool matches(item, Map matchState) => identical(item, _expected);
// If all types were hashable we could show a hash here.
@@ -97,271 +98,6 @@
description.add('same instance as ').addDescriptionOf(_expected);
}
-/// Returns a matcher that matches if the value is structurally equal to
-/// [expected].
-///
-/// If [expected] is a [Matcher], then it matches using that. Otherwise it tests
-/// for equality using `==` on the expected value.
-///
-/// For [Iterable]s and [Map]s, this will recursively match the elements. To
-/// handle cyclic structures a recursion depth [limit] can be provided. The
-/// default limit is 100. [Set]s will be compared order-independently.
-Matcher equals(expected, [int limit = 100]) => expected is String
- ? new _StringEqualsMatcher(expected)
- : new _DeepMatcher(expected, limit);
-
-typedef _RecursiveMatcher = List<String> Function(
- dynamic, dynamic, String, int);
-
-class _DeepMatcher extends Matcher {
- final _expected;
- final int _limit;
-
- _DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit;
-
- // Returns a pair (reason, location)
- List<String> _compareIterables(Iterable expected, Object actual,
- _RecursiveMatcher matcher, int depth, String location) {
- if (actual is Iterable) {
- var expectedIterator = expected.iterator;
- var actualIterator = actual.iterator;
- for (var index = 0;; index++) {
- // Advance in lockstep.
- var expectedNext = expectedIterator.moveNext();
- var actualNext = actualIterator.moveNext();
-
- // If we reached the end of both, we succeeded.
- if (!expectedNext && !actualNext) return null;
-
- // Fail if their lengths are different.
- var newLocation = '$location[$index]';
- if (!expectedNext) return ['longer than expected', newLocation];
- if (!actualNext) return ['shorter than expected', newLocation];
-
- // Match the elements.
- var rp = matcher(expectedIterator.current, actualIterator.current,
- newLocation, depth);
- if (rp != null) return rp;
- }
- } else {
- return ['is not Iterable', location];
- }
- }
-
- List<String> _compareSets(Set expected, Object actual,
- _RecursiveMatcher matcher, int depth, String location) {
- if (actual is Iterable) {
- Set other = actual.toSet();
-
- for (var expectedElement in expected) {
- if (other.every((actualElement) =>
- matcher(expectedElement, actualElement, location, depth) != null)) {
- return ['does not contain $expectedElement', location];
- }
- }
-
- if (other.length > expected.length) {
- return ['larger than expected', location];
- } else if (other.length < expected.length) {
- return ['smaller than expected', location];
- } else {
- return null;
- }
- } else {
- return ['is not Iterable', location];
- }
- }
-
- List<String> _recursiveMatch(
- Object expected, Object actual, String location, int depth) {
- // If the expected value is a matcher, try to match it.
- if (expected is Matcher) {
- var matchState = {};
- if (expected.matches(actual, matchState)) return null;
-
- var description = new StringDescription();
- expected.describe(description);
- return ['does not match $description', location];
- } else {
- // Otherwise, test for equality.
- try {
- if (expected == actual) return null;
- } catch (e) {
- // TODO(gram): Add a test for this case.
- return ['== threw "$e"', location];
- }
- }
-
- if (depth > _limit) return ['recursion depth limit exceeded', location];
-
- // If _limit is 1 we can only recurse one level into object.
- if (depth == 0 || _limit > 1) {
- if (expected is Set) {
- return _compareSets(
- expected, actual, _recursiveMatch, depth + 1, location);
- } else if (expected is Iterable) {
- return _compareIterables(
- expected, actual, _recursiveMatch, depth + 1, location);
- } else if (expected is Map) {
- if (actual is! Map) return ['expected a map', location];
- var map = (actual as Map);
- var err =
- (expected.length == map.length) ? '' : 'has different length and ';
- for (var key in expected.keys) {
- if (!map.containsKey(key)) {
- return ["${err}is missing map key '$key'", location];
- }
- }
-
- for (var key in map.keys) {
- if (!expected.containsKey(key)) {
- return ["${err}has extra map key '$key'", location];
- }
- }
-
- for (var key in expected.keys) {
- var rp = _recursiveMatch(
- expected[key], map[key], "$location['$key']", depth + 1);
- if (rp != null) return rp;
- }
-
- return null;
- }
- }
-
- var description = new StringDescription();
-
- // If we have recursed, show the expected value too; if not, expect() will
- // show it for us.
- if (depth > 0) {
- description
- .add('was ')
- .addDescriptionOf(actual)
- .add(' instead of ')
- .addDescriptionOf(expected);
- return [description.toString(), location];
- }
-
- // We're not adding any value to the actual value.
- return ["", location];
- }
-
- String _match(expected, actual, Map matchState) {
- var rp = _recursiveMatch(expected, actual, '', 0);
- if (rp == null) return null;
- String reason;
- if (rp[0].length > 0) {
- if (rp[1].length > 0) {
- reason = "${rp[0]} at location ${rp[1]}";
- } else {
- reason = rp[0];
- }
- } else {
- reason = '';
- }
- // Cache the failure reason in the matchState.
- addStateInfo(matchState, {'reason': reason});
- return reason;
- }
-
- bool matches(item, Map matchState) =>
- _match(_expected, item, matchState) == null;
-
- Description describe(Description description) =>
- description.addDescriptionOf(_expected);
-
- Description describeMismatch(
- item, Description mismatchDescription, Map matchState, bool verbose) {
- var reason = matchState['reason'] ?? '';
- // If we didn't get a good reason, that would normally be a
- // simple 'is <value>' message. We only add that if the mismatch
- // description is non empty (so we are supplementing the mismatch
- // description).
- if (reason.length == 0 && mismatchDescription.length > 0) {
- mismatchDescription.add('is ').addDescriptionOf(item);
- } else {
- mismatchDescription.add(reason);
- }
- return mismatchDescription;
- }
-}
-
-/// A special equality matcher for strings.
-class _StringEqualsMatcher extends Matcher {
- final String _value;
-
- _StringEqualsMatcher(this._value);
-
- bool get showActualValue => true;
-
- bool matches(item, Map matchState) => _value == item;
-
- Description describe(Description description) =>
- description.addDescriptionOf(_value);
-
- Description describeMismatch(
- item, Description mismatchDescription, Map matchState, bool verbose) {
- if (item is! String) {
- return mismatchDescription.addDescriptionOf(item).add('is not a string');
- } else {
- var buff = new StringBuffer();
- buff.write('is different.');
- var escapedItem = escape(item);
- var escapedValue = escape(_value);
- int minLength = escapedItem.length < escapedValue.length
- ? escapedItem.length
- : escapedValue.length;
- var start = 0;
- for (; start < minLength; start++) {
- if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) {
- break;
- }
- }
- if (start == minLength) {
- if (escapedValue.length < escapedItem.length) {
- buff.write(' Both strings start the same, but the actual value also'
- ' has the following trailing characters: ');
- _writeTrailing(buff, escapedItem, escapedValue.length);
- } else {
- buff.write(' Both strings start the same, but the actual value is'
- ' missing the following trailing characters: ');
- _writeTrailing(buff, escapedValue, escapedItem.length);
- }
- } else {
- buff.write('\nExpected: ');
- _writeLeading(buff, escapedValue, start);
- _writeTrailing(buff, escapedValue, start);
- buff.write('\n Actual: ');
- _writeLeading(buff, escapedItem, start);
- _writeTrailing(buff, escapedItem, start);
- buff.write('\n ');
- for (int i = (start > 10 ? 14 : start); i > 0; i--) buff.write(' ');
- buff.write('^\n Differ at offset $start');
- }
-
- return mismatchDescription.add(buff.toString());
- }
- }
-
- static void _writeLeading(StringBuffer buff, String s, int start) {
- if (start > 10) {
- buff.write('... ');
- buff.write(s.substring(start - 10, start));
- } else {
- buff.write(s.substring(0, start));
- }
- }
-
- static void _writeTrailing(StringBuffer buff, String s, int start) {
- if (start + 10 > s.length) {
- buff.write(s.substring(start));
- } else {
- buff.write(s.substring(start, start + 10));
- buff.write(' ...');
- }
- }
-}
-
/// A matcher that matches any value.
const Matcher anything = const _IsAnything();
@@ -371,23 +107,14 @@
Description describe(Description description) => description.add('anything');
}
+/// **DEPRECATED** Use [TypeMatcher] instead.
+///
/// Returns a matcher that matches if an object is an instance
/// of [T] (or a subtype).
-///
-/// As types are not first class objects in Dart we can only
-/// approximate this test by using a generic wrapper class.
-///
-/// For example, to test whether 'bar' is an instance of type
-/// 'Foo', we would write:
-///
-/// expect(bar, new isInstanceOf<Foo>());
-class isInstanceOf<T> extends Matcher {
+@Deprecated('Use `const TypeMatcher<MyType>()` instead.')
+// ignore: camel_case_types
+class isInstanceOf<T> extends TypeMatcher<T> {
const isInstanceOf();
-
- bool matches(obj, Map matchState) => obj is T;
-
- Description describe(Description description) =>
- description.add('an instance of $T');
}
/// A matcher that matches a function call against no exception.
@@ -398,10 +125,10 @@
/// a wrapper will have to be created.
const Matcher returnsNormally = const _ReturnsNormally();
-class _ReturnsNormally extends Matcher {
+class _ReturnsNormally extends FeatureMatcher<Function> {
const _ReturnsNormally();
- bool matches(f, Map matchState) {
+ bool typedMatches(Function f, Map matchState) {
try {
f();
return true;
@@ -414,8 +141,8 @@
Description describe(Description description) =>
description.add("return normally");
- Description describeMismatch(
- item, Description mismatchDescription, Map matchState, bool verbose) {
+ Description describeTypedMismatch(Function item,
+ Description mismatchDescription, Map matchState, bool verbose) {
mismatchDescription.add('threw ').addDescriptionOf(matchState['exception']);
if (verbose) {
mismatchDescription.add(' at ').add(matchState['stack'].toString());
@@ -424,48 +151,11 @@
}
}
-/*
- * Matchers for different exception types. Ideally we should just be able to
- * use something like:
- *
- * final Matcher throwsException =
- * const _Throws(const isInstanceOf<Exception>());
- *
- * Unfortunately instanceOf is not working with dart2js.
- *
- * Alternatively, if static functions could be used in const expressions,
- * we could use:
- *
- * bool _isException(x) => x is Exception;
- * final Matcher isException = const _Predicate(_isException, "Exception");
- * final Matcher throwsException = const _Throws(isException);
- *
- * But currently using static functions in const expressions is not supported.
- * For now the only solution for all platforms seems to be separate classes
- * for each exception type.
- */
+/// A matcher for [Map].
+const isMap = const TypeMatcher<Map>();
-abstract class TypeMatcher extends Matcher {
- final String _name;
- const TypeMatcher(this._name);
- Description describe(Description description) => description.add(_name);
-}
-
-/// A matcher for Map types.
-const Matcher isMap = const _IsMap();
-
-class _IsMap extends TypeMatcher {
- const _IsMap() : super("Map");
- bool matches(item, Map matchState) => item is Map;
-}
-
-/// A matcher for List types.
-const Matcher isList = const _IsList();
-
-class _IsList extends TypeMatcher {
- const _IsList() : super("List");
- bool matches(item, Map matchState) => item is List;
-}
+/// A matcher for [List].
+const isList = const TypeMatcher<List>();
/// Returns a matcher that matches if an object has a length property
/// that matches [matcher].
@@ -473,7 +163,7 @@
class _HasLength extends Matcher {
final Matcher _matcher;
- const _HasLength([Matcher matcher = null]) : this._matcher = matcher;
+ const _HasLength([Matcher matcher]) : this._matcher = matcher;
bool matches(item, Map matchState) {
try {
@@ -482,8 +172,10 @@
if (item.length * item.length >= 0) {
return _matcher.matches(item.length, matchState);
}
- } catch (e) {}
- return false;
+ } catch (e) {
+ return false;
+ }
+ throw new UnsupportedError('Should never get here');
}
Description describe(Description description) =>
@@ -499,8 +191,10 @@
.add('has length of ')
.addDescriptionOf(item.length);
}
- } catch (e) {}
- return mismatchDescription.add('has no length property');
+ } catch (e) {
+ return mismatchDescription.add('has no length property');
+ }
+ throw new UnsupportedError('Should never get here');
}
}
@@ -514,16 +208,17 @@
Matcher contains(expected) => new _Contains(expected);
class _Contains extends Matcher {
- final _expected;
+ final Object _expected;
const _Contains(this._expected);
bool matches(item, Map matchState) {
+ var expected = _expected;
if (item is String) {
- return item.indexOf(_expected) >= 0;
+ return expected is Pattern && item.contains(expected);
} else if (item is Iterable) {
- if (_expected is Matcher) {
- return item.any((e) => _expected.matches(e, matchState));
+ if (expected is Matcher) {
+ return item.any((e) => expected.matches(e, matchState));
} else {
return item.contains(_expected);
}
@@ -549,26 +244,29 @@
/// Returns a matcher that matches if the match argument is in
/// the expected value. This is the converse of [contains].
-Matcher isIn(expected) => new _In(expected);
-
-class _In extends Matcher {
- final _expected;
-
- const _In(this._expected);
-
- bool matches(item, Map matchState) {
- if (_expected is String) {
- return _expected.indexOf(item) >= 0;
- } else if (_expected is Iterable) {
- return _expected.any((e) => e == item);
- } else if (_expected is Map) {
- return _expected.containsKey(item);
- }
- return false;
+Matcher isIn(expected) {
+ if (expected is Iterable) {
+ return new _In(expected, expected.contains);
+ } else if (expected is String) {
+ return new _In<Pattern>(expected, expected.contains);
+ } else if (expected is Map) {
+ return new _In(expected, expected.containsKey);
}
+ throw new ArgumentError.value(
+ expected, 'expected', 'Only Iterable, Map, and String are supported.');
+}
+
+class _In<T> extends FeatureMatcher<T> {
+ final Object _source;
+ final bool Function(T) _containsFunction;
+
+ const _In(this._source, this._containsFunction);
+
+ bool typedMatches(T item, Map matchState) => _containsFunction(item);
+
Description describe(Description description) =>
- description.add('is in ').addDescriptionOf(_expected);
+ description.add('is in ').addDescriptionOf(_source);
}
/// Returns a matcher that uses an arbitrary function that returns
@@ -583,99 +281,14 @@
typedef bool _PredicateFunction<T>(T value);
-class _Predicate<T> extends Matcher {
+class _Predicate<T> extends FeatureMatcher<T> {
final _PredicateFunction<T> _matcher;
final String _description;
_Predicate(this._matcher, this._description);
- bool matches(item, Map matchState) => _matcher(item as T);
+ bool typedMatches(T item, Map matchState) => _matcher(item);
Description describe(Description description) =>
description.add(_description);
}
-
-/// A useful utility class for implementing other matchers through inheritance.
-/// Derived classes should call the base constructor with a feature name and
-/// description, and an instance matcher, and should implement the
-/// [featureValueOf] abstract method.
-///
-/// The feature description will typically describe the item and the feature,
-/// while the feature name will just name the feature. For example, we may
-/// have a Widget class where each Widget has a price; we could make a
-/// [CustomMatcher] that can make assertions about prices with:
-///
-/// ```dart
-/// class HasPrice extends CustomMatcher {
-/// HasPrice(matcher) : super("Widget with price that is", "price", matcher);
-/// featureValueOf(actual) => actual.price;
-/// }
-/// ```
-///
-/// and then use this for example like:
-///
-/// ```dart
-/// expect(inventoryItem, new HasPrice(greaterThan(0)));
-/// ```
-class CustomMatcher extends Matcher {
- final String _featureDescription;
- final String _featureName;
- final Matcher _matcher;
-
- CustomMatcher(this._featureDescription, this._featureName, matcher)
- : this._matcher = wrapMatcher(matcher);
-
- /// Override this to extract the interesting feature.
- featureValueOf(actual) => actual;
-
- bool matches(item, Map matchState) {
- try {
- var f = featureValueOf(item);
- if (_matcher.matches(f, matchState)) return true;
- addStateInfo(matchState, {'custom.feature': f});
- } catch (exception, stack) {
- addStateInfo(matchState, {
- 'custom.exception': exception.toString(),
- 'custom.stack': new Chain.forTrace(stack)
- .foldFrames(
- (frame) =>
- frame.package == 'test' ||
- frame.package == 'stream_channel' ||
- frame.package == 'matcher',
- terse: true)
- .toString()
- });
- }
- return false;
- }
-
- Description describe(Description description) =>
- description.add(_featureDescription).add(' ').addDescriptionOf(_matcher);
-
- Description describeMismatch(
- item, Description mismatchDescription, Map matchState, bool verbose) {
- if (matchState['custom.exception'] != null) {
- mismatchDescription
- .add('threw ')
- .addDescriptionOf(matchState['custom.exception'])
- .add('\n')
- .add(matchState['custom.stack'].toString());
- return mismatchDescription;
- }
-
- mismatchDescription
- .add('has ')
- .add(_featureName)
- .add(' with value ')
- .addDescriptionOf(matchState['custom.feature']);
- var innerDescription = new StringDescription();
-
- _matcher.describeMismatch(matchState['custom.feature'], innerDescription,
- matchState['state'], verbose);
-
- if (innerDescription.length > 0) {
- mismatchDescription.add(' which ').add(innerDescription.toString());
- }
- return mismatchDescription;
- }
-}
diff --git a/packages/matcher/lib/src/custom_matcher.dart b/packages/matcher/lib/src/custom_matcher.dart
new file mode 100644
index 0000000..862d133
--- /dev/null
+++ b/packages/matcher/lib/src/custom_matcher.dart
@@ -0,0 +1,94 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:stack_trace/stack_trace.dart';
+
+import 'description.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// A useful utility class for implementing other matchers through inheritance.
+/// Derived classes should call the base constructor with a feature name and
+/// description, and an instance matcher, and should implement the
+/// [featureValueOf] abstract method.
+///
+/// The feature description will typically describe the item and the feature,
+/// while the feature name will just name the feature. For example, we may
+/// have a Widget class where each Widget has a price; we could make a
+/// [CustomMatcher] that can make assertions about prices with:
+///
+/// ```dart
+/// class HasPrice extends CustomMatcher {
+/// HasPrice(matcher) : super("Widget with price that is", "price", matcher);
+/// featureValueOf(actual) => actual.price;
+/// }
+/// ```
+///
+/// and then use this for example like:
+///
+/// ```dart
+/// expect(inventoryItem, new HasPrice(greaterThan(0)));
+/// ```
+class CustomMatcher extends Matcher {
+ final String _featureDescription;
+ final String _featureName;
+ final Matcher _matcher;
+
+ CustomMatcher(this._featureDescription, this._featureName, matcher)
+ : this._matcher = wrapMatcher(matcher);
+
+ /// Override this to extract the interesting feature.
+ Object featureValueOf(actual) => actual;
+
+ bool matches(item, Map matchState) {
+ try {
+ var f = featureValueOf(item);
+ if (_matcher.matches(f, matchState)) return true;
+ addStateInfo(matchState, {'custom.feature': f});
+ } catch (exception, stack) {
+ addStateInfo(matchState, {
+ 'custom.exception': exception.toString(),
+ 'custom.stack': new Chain.forTrace(stack)
+ .foldFrames(
+ (frame) =>
+ frame.package == 'test' ||
+ frame.package == 'stream_channel' ||
+ frame.package == 'matcher',
+ terse: true)
+ .toString()
+ });
+ }
+ return false;
+ }
+
+ Description describe(Description description) =>
+ description.add(_featureDescription).add(' ').addDescriptionOf(_matcher);
+
+ Description describeMismatch(
+ item, Description mismatchDescription, Map matchState, bool verbose) {
+ if (matchState['custom.exception'] != null) {
+ mismatchDescription
+ .add('threw ')
+ .addDescriptionOf(matchState['custom.exception'])
+ .add('\n')
+ .add(matchState['custom.stack'].toString());
+ return mismatchDescription;
+ }
+
+ mismatchDescription
+ .add('has ')
+ .add(_featureName)
+ .add(' with value ')
+ .addDescriptionOf(matchState['custom.feature']);
+ var innerDescription = new StringDescription();
+
+ _matcher.describeMismatch(matchState['custom.feature'], innerDescription,
+ matchState['state'] as Map, verbose);
+
+ if (innerDescription.length > 0) {
+ mismatchDescription.add(' which ').add(innerDescription.toString());
+ }
+ return mismatchDescription;
+ }
+}
diff --git a/packages/matcher/lib/src/equals_matcher.dart b/packages/matcher/lib/src/equals_matcher.dart
new file mode 100644
index 0000000..31e369b
--- /dev/null
+++ b/packages/matcher/lib/src/equals_matcher.dart
@@ -0,0 +1,267 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'description.dart';
+import 'feature_matcher.dart';
+import 'interfaces.dart';
+import 'util.dart';
+
+/// Returns a matcher that matches if the value is structurally equal to
+/// [expected].
+///
+/// If [expected] is a [Matcher], then it matches using that. Otherwise it tests
+/// for equality using `==` on the expected value.
+///
+/// For [Iterable]s and [Map]s, this will recursively match the elements. To
+/// handle cyclic structures a recursion depth [limit] can be provided. The
+/// default limit is 100. [Set]s will be compared order-independently.
+Matcher equals(expected, [int limit = 100]) => expected is String
+ ? new _StringEqualsMatcher(expected)
+ : new _DeepMatcher(expected, limit);
+
+typedef _RecursiveMatcher = List<String> Function(
+ dynamic, dynamic, String, int);
+
+/// A special equality matcher for strings.
+class _StringEqualsMatcher extends FeatureMatcher<String> {
+ final String _value;
+
+ _StringEqualsMatcher(this._value);
+
+ bool typedMatches(String item, Map matchState) => _value == item;
+
+ Description describe(Description description) =>
+ description.addDescriptionOf(_value);
+
+ Description describeTypedMismatch(String item,
+ Description mismatchDescription, Map matchState, bool verbose) {
+ var buff = new StringBuffer();
+ buff.write('is different.');
+ var escapedItem = escape(item);
+ var escapedValue = escape(_value);
+ var minLength = escapedItem.length < escapedValue.length
+ ? escapedItem.length
+ : escapedValue.length;
+ var start = 0;
+ for (; start < minLength; start++) {
+ if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) {
+ break;
+ }
+ }
+ if (start == minLength) {
+ if (escapedValue.length < escapedItem.length) {
+ buff.write(' Both strings start the same, but the actual value also'
+ ' has the following trailing characters: ');
+ _writeTrailing(buff, escapedItem, escapedValue.length);
+ } else {
+ buff.write(' Both strings start the same, but the actual value is'
+ ' missing the following trailing characters: ');
+ _writeTrailing(buff, escapedValue, escapedItem.length);
+ }
+ } else {
+ buff.write('\nExpected: ');
+ _writeLeading(buff, escapedValue, start);
+ _writeTrailing(buff, escapedValue, start);
+ buff.write('\n Actual: ');
+ _writeLeading(buff, escapedItem, start);
+ _writeTrailing(buff, escapedItem, start);
+ buff.write('\n ');
+ for (var i = (start > 10 ? 14 : start); i > 0; i--) buff.write(' ');
+ buff.write('^\n Differ at offset $start');
+ }
+
+ return mismatchDescription.add(buff.toString());
+ }
+
+ static void _writeLeading(StringBuffer buff, String s, int start) {
+ if (start > 10) {
+ buff.write('... ');
+ buff.write(s.substring(start - 10, start));
+ } else {
+ buff.write(s.substring(0, start));
+ }
+ }
+
+ static void _writeTrailing(StringBuffer buff, String s, int start) {
+ if (start + 10 > s.length) {
+ buff.write(s.substring(start));
+ } else {
+ buff.write(s.substring(start, start + 10));
+ buff.write(' ...');
+ }
+ }
+}
+
+class _DeepMatcher extends Matcher {
+ final Object _expected;
+ final int _limit;
+
+ _DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit;
+
+ // Returns a pair (reason, location)
+ List<String> _compareIterables(Iterable expected, Object actual,
+ _RecursiveMatcher matcher, int depth, String location) {
+ if (actual is Iterable) {
+ var expectedIterator = expected.iterator;
+ var actualIterator = actual.iterator;
+ for (var index = 0;; index++) {
+ // Advance in lockstep.
+ var expectedNext = expectedIterator.moveNext();
+ var actualNext = actualIterator.moveNext();
+
+ // If we reached the end of both, we succeeded.
+ if (!expectedNext && !actualNext) return null;
+
+ // Fail if their lengths are different.
+ var newLocation = '$location[$index]';
+ if (!expectedNext) return ['longer than expected', newLocation];
+ if (!actualNext) return ['shorter than expected', newLocation];
+
+ // Match the elements.
+ var rp = matcher(expectedIterator.current, actualIterator.current,
+ newLocation, depth);
+ if (rp != null) return rp;
+ }
+ } else {
+ return ['is not Iterable', location];
+ }
+ }
+
+ List<String> _compareSets(Set expected, Object actual,
+ _RecursiveMatcher matcher, int depth, String location) {
+ if (actual is Iterable) {
+ var other = actual.toSet();
+
+ for (var expectedElement in expected) {
+ if (other.every((actualElement) =>
+ matcher(expectedElement, actualElement, location, depth) != null)) {
+ return ['does not contain $expectedElement', location];
+ }
+ }
+
+ if (other.length > expected.length) {
+ return ['larger than expected', location];
+ } else if (other.length < expected.length) {
+ return ['smaller than expected', location];
+ } else {
+ return null;
+ }
+ } else {
+ return ['is not Iterable', location];
+ }
+ }
+
+ List<String> _recursiveMatch(
+ Object expected, Object actual, String location, int depth) {
+ // If the expected value is a matcher, try to match it.
+ if (expected is Matcher) {
+ var matchState = {};
+ if (expected.matches(actual, matchState)) return null;
+
+ var description = new StringDescription();
+ expected.describe(description);
+ return ['does not match $description', location];
+ } else {
+ // Otherwise, test for equality.
+ try {
+ if (expected == actual) return null;
+ } catch (e) {
+ // TODO(gram): Add a test for this case.
+ return ['== threw "$e"', location];
+ }
+ }
+
+ if (depth > _limit) return ['recursion depth limit exceeded', location];
+
+ // If _limit is 1 we can only recurse one level into object.
+ if (depth == 0 || _limit > 1) {
+ if (expected is Set) {
+ return _compareSets(
+ expected, actual, _recursiveMatch, depth + 1, location);
+ } else if (expected is Iterable) {
+ return _compareIterables(
+ expected, actual, _recursiveMatch, depth + 1, location);
+ } else if (expected is Map) {
+ if (actual is! Map) return ['expected a map', location];
+ var map = (actual as Map);
+ var err =
+ (expected.length == map.length) ? '' : 'has different length and ';
+ for (var key in expected.keys) {
+ if (!map.containsKey(key)) {
+ return ["${err}is missing map key '$key'", location];
+ }
+ }
+
+ for (var key in map.keys) {
+ if (!expected.containsKey(key)) {
+ return ["${err}has extra map key '$key'", location];
+ }
+ }
+
+ for (var key in expected.keys) {
+ var rp = _recursiveMatch(
+ expected[key], map[key], "$location['$key']", depth + 1);
+ if (rp != null) return rp;
+ }
+
+ return null;
+ }
+ }
+
+ var description = new StringDescription();
+
+ // If we have recursed, show the expected value too; if not, expect() will
+ // show it for us.
+ if (depth > 0) {
+ description
+ .add('was ')
+ .addDescriptionOf(actual)
+ .add(' instead of ')
+ .addDescriptionOf(expected);
+ return [description.toString(), location];
+ }
+
+ // We're not adding any value to the actual value.
+ return ["", location];
+ }
+
+ String _match(expected, actual, Map matchState) {
+ var rp = _recursiveMatch(expected, actual, '', 0);
+ if (rp == null) return null;
+ String reason;
+ if (rp[0].isNotEmpty) {
+ if (rp[1].isNotEmpty) {
+ reason = "${rp[0]} at location ${rp[1]}";
+ } else {
+ reason = rp[0];
+ }
+ } else {
+ reason = '';
+ }
+ // Cache the failure reason in the matchState.
+ addStateInfo(matchState, {'reason': reason});
+ return reason;
+ }
+
+ bool matches(item, Map matchState) =>
+ _match(_expected, item, matchState) == null;
+
+ Description describe(Description description) =>
+ description.addDescriptionOf(_expected);
+
+ Description describeMismatch(
+ item, Description mismatchDescription, Map matchState, bool verbose) {
+ var reason = matchState['reason'] as String ?? '';
+ // If we didn't get a good reason, that would normally be a
+ // simple 'is <value>' message. We only add that if the mismatch
+ // description is non empty (so we are supplementing the mismatch
+ // description).
+ if (reason.isEmpty && mismatchDescription.length > 0) {
+ mismatchDescription.add('is ').addDescriptionOf(item);
+ } else {
+ mismatchDescription.add(reason);
+ }
+ return mismatchDescription;
+ }
+}
diff --git a/packages/matcher/lib/src/error_matchers.dart b/packages/matcher/lib/src/error_matchers.dart
index 1f37538..eb185f4 100644
--- a/packages/matcher/lib/src/error_matchers.dart
+++ b/packages/matcher/lib/src/error_matchers.dart
@@ -2,94 +2,39 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-import 'core_matchers.dart';
-import 'interfaces.dart';
+import 'type_matcher.dart';
-/// A matcher for ArgumentErrors.
-const Matcher isArgumentError = const _ArgumentError();
+/// A matcher for [ArgumentError].
+const isArgumentError = const TypeMatcher<ArgumentError>();
-class _ArgumentError extends TypeMatcher {
- const _ArgumentError() : super("ArgumentError");
- bool matches(item, Map matchState) => item is ArgumentError;
-}
+/// A matcher for [ConcurrentModificationError].
+const isConcurrentModificationError =
+ const TypeMatcher<ConcurrentModificationError>();
-/// A matcher for ConcurrentModificationError.
-const Matcher isConcurrentModificationError =
- const _ConcurrentModificationError();
+/// A matcher for [CyclicInitializationError].
+const isCyclicInitializationError =
+ const TypeMatcher<CyclicInitializationError>();
-class _ConcurrentModificationError extends TypeMatcher {
- const _ConcurrentModificationError() : super("ConcurrentModificationError");
- bool matches(item, Map matchState) => item is ConcurrentModificationError;
-}
+/// A matcher for [Exception].
+const isException = const TypeMatcher<Exception>();
-/// A matcher for CyclicInitializationError.
-const Matcher isCyclicInitializationError = const _CyclicInitializationError();
+/// A matcher for [FormatException].
+const isFormatException = const TypeMatcher<FormatException>();
-class _CyclicInitializationError extends TypeMatcher {
- const _CyclicInitializationError() : super("CyclicInitializationError");
- bool matches(item, Map matchState) => item is CyclicInitializationError;
-}
+/// A matcher for [NoSuchMethodError].
+const isNoSuchMethodError = const TypeMatcher<NoSuchMethodError>();
-/// A matcher for Exceptions.
-const Matcher isException = const _Exception();
+/// A matcher for [NullThrownError].
+const isNullThrownError = const TypeMatcher<NullThrownError>();
-class _Exception extends TypeMatcher {
- const _Exception() : super("Exception");
- bool matches(item, Map matchState) => item is Exception;
-}
+/// A matcher for [RangeError].
+const isRangeError = const TypeMatcher<RangeError>();
-/// A matcher for FormatExceptions.
-const Matcher isFormatException = const _FormatException();
+/// A matcher for [StateError].
+const isStateError = const TypeMatcher<StateError>();
-class _FormatException extends TypeMatcher {
- const _FormatException() : super("FormatException");
- bool matches(item, Map matchState) => item is FormatException;
-}
+/// A matcher for [UnimplementedError].
+const isUnimplementedError = const TypeMatcher<UnimplementedError>();
-/// A matcher for NoSuchMethodErrors.
-const Matcher isNoSuchMethodError = const _NoSuchMethodError();
-
-class _NoSuchMethodError extends TypeMatcher {
- const _NoSuchMethodError() : super("NoSuchMethodError");
- bool matches(item, Map matchState) => item is NoSuchMethodError;
-}
-
-/// A matcher for NullThrownError.
-const Matcher isNullThrownError = const _NullThrownError();
-
-class _NullThrownError extends TypeMatcher {
- const _NullThrownError() : super("NullThrownError");
- bool matches(item, Map matchState) => item is NullThrownError;
-}
-
-/// A matcher for RangeErrors.
-const Matcher isRangeError = const _RangeError();
-
-class _RangeError extends TypeMatcher {
- const _RangeError() : super("RangeError");
- bool matches(item, Map matchState) => item is RangeError;
-}
-
-/// A matcher for StateErrors.
-const Matcher isStateError = const _StateError();
-
-class _StateError extends TypeMatcher {
- const _StateError() : super("StateError");
- bool matches(item, Map matchState) => item is StateError;
-}
-
-/// A matcher for UnimplementedErrors.
-const Matcher isUnimplementedError = const _UnimplementedError();
-
-class _UnimplementedError extends TypeMatcher {
- const _UnimplementedError() : super("UnimplementedError");
- bool matches(item, Map matchState) => item is UnimplementedError;
-}
-
-/// A matcher for UnsupportedError.
-const Matcher isUnsupportedError = const _UnsupportedError();
-
-class _UnsupportedError extends TypeMatcher {
- const _UnsupportedError() : super("UnsupportedError");
- bool matches(item, Map matchState) => item is UnsupportedError;
-}
+/// A matcher for [UnsupportedError].
+const isUnsupportedError = const TypeMatcher<UnsupportedError>();
diff --git a/packages/matcher/lib/src/feature_matcher.dart b/packages/matcher/lib/src/feature_matcher.dart
new file mode 100644
index 0000000..520e441
--- /dev/null
+++ b/packages/matcher/lib/src/feature_matcher.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'interfaces.dart';
+import 'type_matcher.dart';
+
+/// A package-private [TypeMatcher] implementation that makes it easy for
+/// subclasses to validate aspects of specific types while providing consistent
+/// type checking.
+abstract class FeatureMatcher<T> extends TypeMatcher<T> {
+ const FeatureMatcher();
+
+ bool matches(item, Map matchState) =>
+ super.matches(item, matchState) && typedMatches(item, matchState);
+
+ bool typedMatches(T item, Map matchState);
+
+ Description describeMismatch(
+ item, Description mismatchDescription, Map matchState, bool verbose) {
+ if (item is T) {
+ return describeTypedMismatch(
+ item, mismatchDescription, matchState, verbose);
+ }
+
+ return super.describe(mismatchDescription.add('not an '));
+ }
+
+ Description describeTypedMismatch(T item, Description mismatchDescription,
+ Map matchState, bool verbose) =>
+ mismatchDescription;
+}
diff --git a/packages/matcher/lib/src/having_matcher.dart b/packages/matcher/lib/src/having_matcher.dart
new file mode 100644
index 0000000..1684a93
--- /dev/null
+++ b/packages/matcher/lib/src/having_matcher.dart
@@ -0,0 +1,62 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'custom_matcher.dart';
+import 'interfaces.dart';
+import 'type_matcher.dart';
+import 'util.dart';
+
+/// A package-private [TypeMatcher] implementation that handles is returned
+/// by calls to [TypeMatcher.having].
+class HavingMatcher<T> implements TypeMatcher<T> {
+ final TypeMatcher<T> _parent;
+ final List<_FunctionMatcher> _functionMatchers;
+
+ HavingMatcher(TypeMatcher<T> parent, String description,
+ Object feature(T source), Object matcher,
+ [Iterable<_FunctionMatcher> existing])
+ : this._parent = parent,
+ this._functionMatchers = <_FunctionMatcher>[]
+ ..addAll(existing ?? [])
+ ..add(new _FunctionMatcher<T>(description, feature, matcher));
+
+ TypeMatcher<T> having(
+ Object feature(T source), String description, Object matcher) =>
+ new HavingMatcher(
+ _parent, description, feature, matcher, _functionMatchers);
+
+ bool matches(item, Map matchState) {
+ for (var matcher in <Matcher>[_parent].followedBy(_functionMatchers)) {
+ if (!matcher.matches(item, matchState)) {
+ addStateInfo(matchState, {'matcher': matcher});
+ return false;
+ }
+ }
+ return true;
+ }
+
+ Description describeMismatch(
+ item, Description mismatchDescription, Map matchState, bool verbose) {
+ var matcher = matchState['matcher'] as Matcher;
+ matcher.describeMismatch(
+ item, mismatchDescription, matchState['state'] as Map, verbose);
+ return mismatchDescription;
+ }
+
+ Description describe(Description description) => description
+ .add('')
+ .addDescriptionOf(_parent)
+ .add(' with ')
+ .addAll('', ' and ', '', _functionMatchers);
+}
+
+class _FunctionMatcher<T> extends CustomMatcher {
+ final dynamic Function(T value) _feature;
+
+ _FunctionMatcher(String name, this._feature, matcher)
+ : super('`$name`:', '`$name`', matcher);
+
+ @override
+ Object featureValueOf(covariant T actual) => _feature(actual);
+}
diff --git a/packages/matcher/lib/src/interfaces.dart b/packages/matcher/lib/src/interfaces.dart
index 6ab0f14..1f39fa4 100644
--- a/packages/matcher/lib/src/interfaces.dart
+++ b/packages/matcher/lib/src/interfaces.dart
@@ -2,14 +2,12 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-// To decouple the reporting of errors, and allow for extensibility of
-// matchers, we make use of some interfaces.
-
-/// Matchers build up their error messages by appending to
-/// Description objects. This interface is implemented by
-/// StringDescription. This interface is unlikely to need
-/// other implementations, but could be useful to replace in
-/// some cases - e.g. language conversion.
+/// Matchers build up their error messages by appending to Description objects.
+///
+/// This interface is implemented by StringDescription.
+///
+/// This interface is unlikely to need other implementations, but could be
+/// useful to replace in some cases - e.g. language conversion.
abstract class Description {
int get length;
@@ -27,29 +25,32 @@
Description addAll(String start, String separator, String end, Iterable list);
}
-/// [expect] Matchers must implement/extend the Matcher class.
+/// The base class for all matchers.
///
-/// The base Matcher class has a generic implementation of [describeMismatch]
-/// so this does not need to be provided unless a more clear description is
-/// required. The other two methods ([matches] and [describe])
-/// must always be provided as they are highly matcher-specific.
+/// [matches] and [describe] must be implemented by subclasses.
+///
+/// Subclasses can override [describeMismatch] if a more specific description is
+/// required when the matcher fails.
abstract class Matcher {
const Matcher();
- /// This does the matching of the actual vs expected values.
+ /// Does the matching of the actual vs expected values.
+ ///
/// [item] is the actual value. [matchState] can be supplied
/// and may be used to add details about the mismatch that are too
/// costly to determine in [describeMismatch].
bool matches(item, Map matchState);
- /// This builds a textual description of the matcher.
+ /// Builds a textual description of the matcher.
Description describe(Description description);
- /// This builds a textual description of a specific mismatch. [item]
- /// is the value that was tested by [matches]; [matchState] is
+ /// Builds a textual description of a specific mismatch.
+ ///
+ /// [item] is the value that was tested by [matches]; [matchState] is
/// the [Map] that was passed to and supplemented by [matches]
/// with additional information about the mismatch, and [mismatchDescription]
/// is the [Description] that is being built to describe the mismatch.
+ ///
/// A few matchers make use of the [verbose] flag to provide detailed
/// information that is not typically included but can be of help in
/// diagnosing failures, such as stack traces.
diff --git a/packages/matcher/lib/src/iterable_matchers.dart b/packages/matcher/lib/src/iterable_matchers.dart
index a4a122c..02c41aa 100644
--- a/packages/matcher/lib/src/iterable_matchers.dart
+++ b/packages/matcher/lib/src/iterable_matchers.dart
@@ -2,8 +2,9 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-import 'core_matchers.dart';
import 'description.dart';
+import 'equals_matcher.dart';
+import 'feature_matcher.dart';
import 'interfaces.dart';
import 'util.dart';
@@ -16,10 +17,7 @@
_EveryElement(this._matcher);
- bool matches(item, Map matchState) {
- if (item is! Iterable) {
- return false;
- }
+ bool typedMatches(Iterable item, Map matchState) {
var i = 0;
for (var element in item) {
if (!_matcher.matches(element, matchState)) {
@@ -34,7 +32,7 @@
Description describe(Description description) =>
description.add('every element(').addDescriptionOf(_matcher).add(')');
- Description describeMismatch(
+ Description describeTypedMismatch(
item, Description mismatchDescription, Map matchState, bool verbose) {
if (matchState['index'] != null) {
var index = matchState['index'];
@@ -45,7 +43,7 @@
.add(' which ');
var subDescription = new StringDescription();
_matcher.describeMismatch(
- element, subDescription, matchState['state'], verbose);
+ element, subDescription, matchState['state'] as Map, verbose);
if (subDescription.length > 0) {
mismatchDescription.add(subDescription.toString());
} else {
@@ -69,9 +67,8 @@
_AnyElement(this._matcher);
- bool matches(item, Map matchState) {
- return item.any((e) => _matcher.matches(e, matchState));
- }
+ bool typedMatches(Iterable item, Map matchState) =>
+ item.any((e) => _matcher.matches(e, matchState));
Description describe(Description description) =>
description.add('some element ').addDescriptionOf(_matcher);
@@ -83,28 +80,22 @@
/// This is equivalent to [equals] but does not recurse.
Matcher orderedEquals(Iterable expected) => new _OrderedEquals(expected);
-class _OrderedEquals extends Matcher {
+class _OrderedEquals extends _IterableMatcher {
final Iterable _expected;
- Matcher _matcher;
+ final Matcher _matcher;
- _OrderedEquals(this._expected) {
- _matcher = equals(_expected, 1);
- }
+ _OrderedEquals(this._expected) : _matcher = equals(_expected, 1);
- bool matches(item, Map matchState) =>
- (item is Iterable) && _matcher.matches(item, matchState);
+ bool typedMatches(Iterable item, Map matchState) =>
+ _matcher.matches(item, matchState);
Description describe(Description description) =>
description.add('equals ').addDescriptionOf(_expected).add(' ordered');
- Description describeMismatch(
- item, Description mismatchDescription, Map matchState, bool verbose) {
- if (item is! Iterable) {
- return mismatchDescription.add('is not an Iterable');
- } else {
- return _matcher.describeMismatch(
- item, mismatchDescription, matchState, verbose);
- }
+ Description describeTypedMismatch(Iterable item,
+ Description mismatchDescription, Map matchState, bool verbose) {
+ return _matcher.describeMismatch(
+ item, mismatchDescription, matchState, verbose);
}
}
@@ -130,17 +121,8 @@
/// Iterable matchers match against [Iterable]s. We add this intermediate
/// class to give better mismatch error messages than the base Matcher class.
-abstract class _IterableMatcher extends Matcher {
+abstract class _IterableMatcher extends FeatureMatcher<Iterable> {
const _IterableMatcher();
- Description describeMismatch(
- item, Description mismatchDescription, Map matchState, bool verbose) {
- if (item is! Iterable) {
- return mismatchDescription.addDescriptionOf(item).add(' not an Iterable');
- } else {
- return super
- .describeMismatch(item, mismatchDescription, matchState, verbose);
- }
- }
}
/// Returns a matcher which matches [Iterable]s whose elements match the
@@ -150,7 +132,7 @@
/// only be used on small iterables.
Matcher unorderedMatches(Iterable expected) => new _UnorderedMatches(expected);
-class _UnorderedMatches extends Matcher {
+class _UnorderedMatches extends _IterableMatcher {
final List<Matcher> _expected;
final bool _allowUnmatchedValues;
@@ -158,11 +140,7 @@
: _expected = expected.map(wrapMatcher).toList(),
_allowUnmatchedValues = allowUnmatchedValues ?? false;
- String _test(item) {
- if (item is! Iterable) return 'not iterable';
-
- var values = item.toList();
-
+ String _test(List values) {
// Check the lengths are the same.
if (_expected.length > values.length) {
return 'has too few elements (${values.length} < ${_expected.length})';
@@ -172,8 +150,8 @@
var edges =
new List.generate(values.length, (_) => <int>[], growable: false);
- for (int v = 0; v < values.length; v++) {
- for (int m = 0; m < _expected.length; m++) {
+ for (var v = 0; v < values.length; v++) {
+ for (var m = 0; m < _expected.length; m++) {
if (_expected[m].matches(values[v], {})) {
edges[v].add(m);
}
@@ -182,10 +160,10 @@
// The index into `values` matched with each matcher or `null` if no value
// has been matched yet.
var matched = new List<int>(_expected.length);
- for (int valueIndex = 0; valueIndex < values.length; valueIndex++) {
+ for (var valueIndex = 0; valueIndex < values.length; valueIndex++) {
_findPairing(edges, valueIndex, matched);
}
- for (int matcherIndex = 0;
+ for (var matcherIndex = 0;
matcherIndex < _expected.length;
matcherIndex++) {
if (matched[matcherIndex] == null) {
@@ -205,21 +183,22 @@
return null;
}
- bool matches(item, Map mismatchState) => _test(item) == null;
+ bool typedMatches(Iterable item, Map mismatchState) =>
+ _test(item.toList()) == null;
Description describe(Description description) => description
.add('matches ')
.addAll('[', ', ', ']', _expected)
.add(' unordered');
- Description describeMismatch(item, Description mismatchDescription,
+ Description describeTypedMismatch(item, Description mismatchDescription,
Map matchState, bool verbose) =>
- mismatchDescription.add(_test(item));
+ mismatchDescription.add(_test(item.toList()));
- /// Returns [true] if the value at [valueIndex] can be paired with some
+ /// Returns `true` if the value at [valueIndex] can be paired with some
/// unmatched matcher and updates the state of [matched].
///
- /// If there is a conflic where multiple values may match the same matcher
+ /// If there is a conflict where multiple values may match the same matcher
/// recursively looks for a new place to match the old value. [reserved]
/// tracks the matchers that have been used _during_ this search.
bool _findPairing(List<List<int>> edges, int valueIndex, List<int> matched,
@@ -260,34 +239,28 @@
_PairwiseCompare(this._expected, this._comparator, this._description);
- bool matches(item, Map matchState) {
- if (item is Iterable) {
- if (item.length != _expected.length) return false;
- var iterator = item.iterator;
- var i = 0;
- for (var e in _expected) {
- iterator.moveNext();
- if (!_comparator(e, iterator.current)) {
- addStateInfo(matchState,
- {'index': i, 'expected': e, 'actual': iterator.current});
- return false;
- }
- i++;
+ bool typedMatches(Iterable item, Map matchState) {
+ if (item.length != _expected.length) return false;
+ var iterator = item.iterator;
+ var i = 0;
+ for (var e in _expected) {
+ iterator.moveNext();
+ if (!_comparator(e, iterator.current as T)) {
+ addStateInfo(matchState,
+ {'index': i, 'expected': e, 'actual': iterator.current});
+ return false;
}
- return true;
- } else {
- return false;
+ i++;
}
+ return true;
}
Description describe(Description description) =>
description.add('pairwise $_description ').addDescriptionOf(_expected);
- Description describeMismatch(
- item, Description mismatchDescription, Map matchState, bool verbose) {
- if (item is! Iterable) {
- return mismatchDescription.add('is not an Iterable');
- } else if (item.length != _expected.length) {
+ Description describeTypedMismatch(Iterable item,
+ Description mismatchDescription, Map matchState, bool verbose) {
+ if (item.length != _expected.length) {
return mismatchDescription
.add('has length ${item.length} instead of ${_expected.length}');
} else {
@@ -342,13 +315,12 @@
Matcher containsAllInOrder(Iterable expected) =>
new _ContainsAllInOrder(expected);
-class _ContainsAllInOrder implements Matcher {
+class _ContainsAllInOrder extends _IterableMatcher {
final Iterable _expected;
_ContainsAllInOrder(this._expected);
- String _test(item, Map matchState) {
- if (item is! Iterable) return 'not an iterable';
+ String _test(Iterable item, Map matchState) {
var matchers = _expected.map(wrapMatcher).toList();
var matcherIndex = 0;
for (var value in item) {
@@ -363,7 +335,8 @@
}
@override
- bool matches(item, Map matchState) => _test(item, matchState) == null;
+ bool typedMatches(Iterable item, Map matchState) =>
+ _test(item, matchState) == null;
@override
Description describe(Description description) => description
@@ -372,7 +345,7 @@
.add(')');
@override
- Description describeMismatch(item, Description mismatchDescription,
- Map matchState, bool verbose) =>
+ Description describeTypedMismatch(Iterable item,
+ Description mismatchDescription, Map matchState, bool verbose) =>
mismatchDescription.add(_test(item, matchState));
}
diff --git a/packages/matcher/lib/src/map_matchers.dart b/packages/matcher/lib/src/map_matchers.dart
index 47cddef..9825c9b 100644
--- a/packages/matcher/lib/src/map_matchers.dart
+++ b/packages/matcher/lib/src/map_matchers.dart
@@ -9,7 +9,7 @@
Matcher containsValue(value) => new _ContainsValue(value);
class _ContainsValue extends Matcher {
- final _value;
+ final Object _value;
const _ContainsValue(this._value);
@@ -24,7 +24,7 @@
new _ContainsMapping(key, wrapMatcher(value));
class _ContainsMapping extends Matcher {
- final _key;
+ final Object _key;
final Matcher _valueMatcher;
const _ContainsMapping(this._key, this._valueMatcher);
diff --git a/packages/matcher/lib/src/numeric_matchers.dart b/packages/matcher/lib/src/numeric_matchers.dart
index c405391..4187078 100644
--- a/packages/matcher/lib/src/numeric_matchers.dart
+++ b/packages/matcher/lib/src/numeric_matchers.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'feature_matcher.dart';
import 'interfaces.dart';
/// Returns a matcher which matches if the match argument is within [delta]
@@ -11,19 +12,15 @@
/// than or equal [value]-[delta] and less than or equal to [value]+[delta].
Matcher closeTo(num value, num delta) => new _IsCloseTo(value, delta);
-class _IsCloseTo extends Matcher {
+class _IsCloseTo extends FeatureMatcher<num> {
final num _value, _delta;
const _IsCloseTo(this._value, this._delta);
- bool matches(item, Map matchState) {
- if (item is num) {
- var diff = item - _value;
- if (diff < 0) diff = -diff;
- return (diff <= _delta);
- } else {
- return false;
- }
+ bool typedMatches(item, Map matchState) {
+ var diff = item - _value;
+ if (diff < 0) diff = -diff;
+ return (diff <= _delta);
}
Description describe(Description description) => description
@@ -32,15 +29,11 @@
.add(' of ')
.addDescriptionOf(_value);
- Description describeMismatch(
+ Description describeTypedMismatch(
item, Description mismatchDescription, Map matchState, bool verbose) {
- if (item is num) {
- var diff = item - _value;
- if (diff < 0) diff = -diff;
- return mismatchDescription.add(' differs by ').addDescriptionOf(diff);
- } else {
- return mismatchDescription.add(' not numeric');
- }
+ var diff = item - _value;
+ if (diff < 0) diff = -diff;
+ return mismatchDescription.add(' differs by ').addDescriptionOf(diff);
}
}
@@ -64,42 +57,28 @@
Matcher inClosedOpenRange(num low, num high) =>
new _InRange(low, high, true, false);
-class _InRange extends Matcher {
+class _InRange extends FeatureMatcher<num> {
final num _low, _high;
final bool _lowMatchValue, _highMatchValue;
const _InRange(
this._low, this._high, this._lowMatchValue, this._highMatchValue);
- bool matches(value, Map matchState) {
- if (value is num) {
- if (value < _low || value > _high) {
- return false;
- }
- if (value == _low) {
- return _lowMatchValue;
- }
- if (value == _high) {
- return _highMatchValue;
- }
- return true;
- } else {
+ bool typedMatches(value, Map matchState) {
+ if (value < _low || value > _high) {
return false;
}
+ if (value == _low) {
+ return _lowMatchValue;
+ }
+ if (value == _high) {
+ return _highMatchValue;
+ }
+ return true;
}
Description describe(Description description) =>
description.add("be in range from "
"$_low (${_lowMatchValue ? 'inclusive' : 'exclusive'}) to "
"$_high (${_highMatchValue ? 'inclusive' : 'exclusive'})");
-
- Description describeMismatch(
- item, Description mismatchDescription, Map matchState, bool verbose) {
- if (item is! num) {
- return mismatchDescription.addDescriptionOf(item).add(' not numeric');
- } else {
- return super
- .describeMismatch(item, mismatchDescription, matchState, verbose);
- }
- }
}
diff --git a/packages/matcher/lib/src/operator_matchers.dart b/packages/matcher/lib/src/operator_matchers.dart
index 5d6baed..827fbf1 100644
--- a/packages/matcher/lib/src/operator_matchers.dart
+++ b/packages/matcher/lib/src/operator_matchers.dart
@@ -106,5 +106,5 @@
args = [arg0, arg1, arg2, arg3, arg4, arg5, arg6].where((e) => e != null);
}
- return args.map((e) => wrapMatcher(e)).toList();
+ return args.map(wrapMatcher).toList();
}
diff --git a/packages/matcher/lib/src/order_matchers.dart b/packages/matcher/lib/src/order_matchers.dart
index b6079e9..829aadb 100644
--- a/packages/matcher/lib/src/order_matchers.dart
+++ b/packages/matcher/lib/src/order_matchers.dart
@@ -52,7 +52,7 @@
// `==` and `<` operators to evaluate the match. Or change the matcher.
class _OrderingMatcher extends Matcher {
/// Expected value.
- final _value;
+ final Object _value;
/// What to return if actual == expected
final bool _equalValue;
diff --git a/packages/matcher/lib/src/pretty_print.dart b/packages/matcher/lib/src/pretty_print.dart
index 826cad0..1cbe139 100644
--- a/packages/matcher/lib/src/pretty_print.dart
+++ b/packages/matcher/lib/src/pretty_print.dart
@@ -125,6 +125,7 @@
// So we play safe here.
try {
if (x == null) return "null";
+ if (x is Type) return "Type";
var type = x.runtimeType.toString();
// TODO(nweiz): if the object's type is private, find a public superclass to
// display once there's a portable API to do that.
diff --git a/packages/matcher/lib/src/string_matchers.dart b/packages/matcher/lib/src/string_matchers.dart
index 14e6fbb..47703c3 100644
--- a/packages/matcher/lib/src/string_matchers.dart
+++ b/packages/matcher/lib/src/string_matchers.dart
@@ -2,13 +2,14 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+import 'feature_matcher.dart';
import 'interfaces.dart';
/// Returns a matcher which matches if the match argument is a string and
/// is equal to [value] when compared case-insensitively.
Matcher equalsIgnoringCase(String value) => new _IsEqualIgnoringCase(value);
-class _IsEqualIgnoringCase extends _StringMatcher {
+class _IsEqualIgnoringCase extends FeatureMatcher<String> {
final String _value;
final String _matchValue;
@@ -16,8 +17,8 @@
: _value = value,
_matchValue = value.toLowerCase();
- bool matches(item, Map matchState) =>
- item is String && _matchValue == item.toLowerCase();
+ bool typedMatches(String item, Map matchState) =>
+ _matchValue == item.toLowerCase();
Description describe(Description description) =>
description.addDescriptionOf(_value).add(' ignoring case');
@@ -43,7 +44,7 @@
Matcher equalsIgnoringWhitespace(String value) =>
new _IsEqualIgnoringWhitespace(value);
-class _IsEqualIgnoringWhitespace extends _StringMatcher {
+class _IsEqualIgnoringWhitespace extends FeatureMatcher<String> {
final String _value;
final String _matchValue;
@@ -51,23 +52,18 @@
: _value = value,
_matchValue = collapseWhitespace(value);
- bool matches(item, Map matchState) =>
- item is String && _matchValue == collapseWhitespace(item);
+ bool typedMatches(String item, Map matchState) =>
+ _matchValue == collapseWhitespace(item);
Description describe(Description description) =>
description.addDescriptionOf(_matchValue).add(' ignoring whitespace');
- Description describeMismatch(
+ Description describeTypedMismatch(
item, Description mismatchDescription, Map matchState, bool verbose) {
- if (item is String) {
- return mismatchDescription
- .add('is ')
- .addDescriptionOf(collapseWhitespace(item))
- .add(' with whitespace compressed');
- } else {
- return super
- .describeMismatch(item, mismatchDescription, matchState, verbose);
- }
+ return mismatchDescription
+ .add('is ')
+ .addDescriptionOf(collapseWhitespace(item))
+ .add(' with whitespace compressed');
}
}
@@ -75,13 +71,12 @@
/// starts with [prefixString].
Matcher startsWith(String prefixString) => new _StringStartsWith(prefixString);
-class _StringStartsWith extends _StringMatcher {
+class _StringStartsWith extends FeatureMatcher<String> {
final String _prefix;
const _StringStartsWith(this._prefix);
- bool matches(item, Map matchState) =>
- item is String && item.startsWith(_prefix);
+ bool typedMatches(item, Map matchState) => item.startsWith(_prefix);
Description describe(Description description) =>
description.add('a string starting with ').addDescriptionOf(_prefix);
@@ -91,13 +86,12 @@
/// ends with [suffixString].
Matcher endsWith(String suffixString) => new _StringEndsWith(suffixString);
-class _StringEndsWith extends _StringMatcher {
+class _StringEndsWith extends FeatureMatcher<String> {
final String _suffix;
const _StringEndsWith(this._suffix);
- bool matches(item, Map matchState) =>
- item is String && item.endsWith(_suffix);
+ bool typedMatches(item, Map matchState) => item.endsWith(_suffix);
Description describe(Description description) =>
description.add('a string ending with ').addDescriptionOf(_suffix);
@@ -112,22 +106,18 @@
Matcher stringContainsInOrder(List<String> substrings) =>
new _StringContainsInOrder(substrings);
-class _StringContainsInOrder extends _StringMatcher {
+class _StringContainsInOrder extends FeatureMatcher<String> {
final List<String> _substrings;
const _StringContainsInOrder(this._substrings);
- bool matches(item, Map matchState) {
- if (item is String) {
- var from_index = 0;
- for (var s in _substrings) {
- from_index = item.indexOf(s, from_index);
- if (from_index < 0) return false;
- }
- return true;
- } else {
- return false;
+ bool typedMatches(item, Map matchState) {
+ var fromIndex = 0;
+ for (var s in _substrings) {
+ fromIndex = item.indexOf(s, fromIndex);
+ if (fromIndex < 0) return false;
}
+ return true;
}
Description describe(Description description) => description.addAll(
@@ -141,7 +131,7 @@
/// used to create a RegExp instance.
Matcher matches(re) => new _MatchesRegExp(re);
-class _MatchesRegExp extends _StringMatcher {
+class _MatchesRegExp extends FeatureMatcher<String> {
RegExp _regexp;
_MatchesRegExp(re) {
@@ -154,28 +144,12 @@
}
}
- bool matches(item, Map matchState) =>
- item is String ? _regexp.hasMatch(item) : false;
+ bool typedMatches(item, Map matchState) => _regexp.hasMatch(item);
Description describe(Description description) =>
description.add("match '${_regexp.pattern}'");
}
-// String matchers match against a string. We add this intermediate
-// class to give better mismatch error messages than the base Matcher class.
-abstract class _StringMatcher extends Matcher {
- const _StringMatcher();
- Description describeMismatch(
- item, Description mismatchDescription, Map matchState, bool verbose) {
- if (!(item is String)) {
- return mismatchDescription.addDescriptionOf(item).add(' not a string');
- } else {
- return super
- .describeMismatch(item, mismatchDescription, matchState, verbose);
- }
- }
-}
-
/// Utility function to collapse whitespace runs to single spaces
/// and strip leading/trailing whitespace.
String collapseWhitespace(String string) {
diff --git a/packages/matcher/lib/src/type_matcher.dart b/packages/matcher/lib/src/type_matcher.dart
new file mode 100644
index 0000000..91552c9
--- /dev/null
+++ b/packages/matcher/lib/src/type_matcher.dart
@@ -0,0 +1,89 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'having_matcher.dart';
+import 'interfaces.dart';
+
+/// A [Matcher] subclass that supports validating the [Type] of the target
+/// object.
+///
+/// ```dart
+/// expect(shouldBeDuration, new TypeMatcher<Duration>());
+/// ```
+///
+/// If you want to further validate attributes of the specified [Type], use the
+/// [having] function.
+///
+/// ```dart
+/// void shouldThrowRangeError(int value) {
+/// throw new RangeError.range(value, 10, 20);
+/// }
+///
+/// expect(
+/// () => shouldThrowRangeError(5),
+/// throwsA(const TypeMatcher<RangeError>()
+/// .having((e) => e.start, 'start', greaterThanOrEqualTo(10))
+/// .having((e) => e.end, 'end', lessThanOrEqualTo(20))));
+/// ```
+///
+/// Notice that you can chain multiple calls to [having] to verify multiple
+/// aspects of an object.
+///
+/// Note: All of the top-level `isType` matchers exposed by this package are
+/// instances of [TypeMatcher], so you can use the [having] function without
+/// creating your own instance.
+///
+/// ```dart
+/// expect(
+/// () => shouldThrowRangeError(5),
+/// throwsA(isRangeError
+/// .having((e) => e.start, 'start', greaterThanOrEqualTo(10))
+/// .having((e) => e.end, 'end', lessThanOrEqualTo(20))));
+/// ```
+class TypeMatcher<T> extends Matcher {
+ final String _name;
+ const TypeMatcher(
+ [@Deprecated('Provide a type argument to TypeMatcher and omit the name. '
+ 'This argument will be removed in the next release.')
+ String name])
+ : this._name =
+ // ignore: deprecated_member_use
+ name;
+
+ /// Returns a new [TypeMatcher] that validates the existing type as well as
+ /// a specific [feature] of the object with the provided [matcher].
+ ///
+ /// Provides a human-readable [description] of the [feature] to make debugging
+ /// failures easier.
+ ///
+ /// ```dart
+ /// /// Validates that the object is a [RangeError] with a message containing
+ /// /// the string 'details' and `start` and `end` properties that are `null`.
+ /// final _rangeMatcher = isRangeError
+ /// .having((e) => e.message, 'message', contains('details'))
+ /// .having((e) => e.start, 'start', isNull)
+ /// .having((e) => e.end, 'end', isNull);
+ /// ```
+ TypeMatcher<T> having(
+ Object feature(T source), String description, Object matcher) =>
+ new HavingMatcher(this, description, feature, matcher);
+
+ Description describe(Description description) {
+ var name = _name ?? _stripDynamic(T);
+ return description.add("<Instance of '$name'>");
+ }
+
+ bool matches(Object item, Map matchState) => item is T;
+}
+
+final _dart2DynamicArgs = new RegExp('<dynamic(, dynamic)*>');
+
+/// With this expression `{}.runtimeType.toString`,
+/// Dart 1: "<Instance of Map>
+/// Dart 2: "<Instance of Map<dynamic, dynamic>>"
+///
+/// This functions returns the Dart 1 output, when Dart 2 runtime semantics
+/// are enabled.
+String _stripDynamic(Type type) =>
+ type.toString().replaceAll(_dart2DynamicArgs, '');
diff --git a/packages/matcher/lib/src/util.dart b/packages/matcher/lib/src/util.dart
index 7e7ff05..1df39ef 100644
--- a/packages/matcher/lib/src/util.dart
+++ b/packages/matcher/lib/src/util.dart
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'core_matchers.dart';
+import 'equals_matcher.dart';
import 'interfaces.dart';
typedef bool _Predicate<T>(T value);
@@ -44,6 +45,7 @@
} else if (x is _Predicate<Null>) {
// x is a unary predicate, but expects a specific type
// so wrap it.
+ // ignore: unnecessary_lambdas
return predicate((a) => (x as dynamic)(a));
} else {
return equals(x);
@@ -65,6 +67,6 @@
/// Given single-character string, return the hex-escaped equivalent.
String _getHexLiteral(String input) {
- int rune = input.runes.single;
+ var rune = input.runes.single;
return r'\x' + rune.toRadixString(16).toUpperCase().padLeft(2, '0');
}
diff --git a/packages/matcher/pubspec.yaml b/packages/matcher/pubspec.yaml
index c898554..8a00849 100644
--- a/packages/matcher/pubspec.yaml
+++ b/packages/matcher/pubspec.yaml
@@ -1,14 +1,15 @@
name: matcher
-version: 0.12.2
-author: Dart Team <misc@dartlang.org>
-description: Support for specifying test expectations
-homepage: https://github.com/dart-lang/matcher
-environment:
- sdk: '>=1.23.0 <2.0.0'
-dependencies:
- stack_trace: '^1.2.0'
-dev_dependencies:
- test: '>=0.12.0 <0.13.0'
+version: 0.12.3+1
-dependency_overrides:
- test: ^0.12.0
+description: Support for specifying test expectations
+author: Dart Team <misc@dartlang.org>
+homepage: https://github.com/dart-lang/matcher
+
+environment:
+ sdk: '>=2.0.0-dev.17.0 <3.0.0'
+
+dependencies:
+ stack_trace: ^1.2.0
+
+dev_dependencies:
+ test: '>=0.12.0 <2.0.0'
diff --git a/packages/matcher/test/core_matchers_test.dart b/packages/matcher/test/core_matchers_test.dart
index 8261c6f..927cc7b 100644
--- a/packages/matcher/test/core_matchers_test.dart
+++ b/packages/matcher/test/core_matchers_test.dart
@@ -7,11 +7,6 @@
import 'test_utils.dart';
-class BadCustomMatcher extends CustomMatcher {
- BadCustomMatcher() : super("feature", "description", {1: "a"});
- featureValueOf(actual) => throw new Exception("bang");
-}
-
void main() {
test('isTrue', () {
shouldPass(true, isTrue);
@@ -35,25 +30,27 @@
});
test('isNaN', () {
- shouldPass(double.NAN, isNaN);
+ shouldPass(double.nan, isNaN);
shouldFail(3.1, isNaN, "Expected: NaN Actual: <3.1>");
+ shouldFail('not a num', isNaN, endsWith('not an <Instance of \'num\'>'));
});
test('isNotNaN', () {
shouldPass(3.1, isNotNaN);
- shouldFail(double.NAN, isNotNaN, "Expected: not NaN Actual: <NaN>");
+ shouldFail(double.nan, isNotNaN, "Expected: not NaN Actual: <NaN>");
+ shouldFail('not a num', isNotNaN, endsWith('not an <Instance of \'num\'>'));
});
test('same', () {
- var a = new Map();
- var b = new Map();
+ var a = {};
+ var b = {};
shouldPass(a, same(a));
shouldFail(b, same(a), "Expected: same instance as {} Actual: {}");
});
test('equals', () {
- var a = new Map();
- var b = new Map();
+ var a = {};
+ var b = {};
shouldPass(a, equals(a));
shouldPass(a, equals(b));
});
@@ -81,7 +78,7 @@
});
test('anything', () {
- var a = new Map();
+ var a = {};
shouldPass(0, anything);
shouldPass(null, anything);
shouldPass(a, anything);
@@ -95,12 +92,14 @@
returnsNormally,
matches(r"Expected: return normally"
r" Actual: <Closure.*>"
- r" Which: threw 'X'"));
+ r" Which: threw StateError:<Bad state: X>"));
+ shouldFail('not a function', returnsNormally,
+ contains('not an <Instance of \'Function\'>'));
});
test('hasLength', () {
- var a = new Map();
- var b = new List();
+ var a = {};
+ var b = [];
shouldPass(a, hasLength(0));
shouldPass(b, hasLength(0));
shouldPass('a', hasLength(1));
@@ -214,42 +213,19 @@
shouldFail(actual3, equals(expected3), reason3);
});
- test('isInstanceOf', () {
- shouldFail(0, new isInstanceOf<String>(),
- "Expected: an instance of String Actual: <0>");
- shouldPass('cow', new isInstanceOf<String>());
- });
-
group('Predicate Matchers', () {
test('isInstanceOf', () {
shouldFail(0, predicate((x) => x is String, "an instance of String"),
"Expected: an instance of String Actual: <0>");
shouldPass('cow', predicate((x) => x is String, "an instance of String"));
+
+ if (isDart2) {
+ // With Dart2 semantics, predicate picks up a type argument of `bool`
+ // and we get nice type checking.
+ // Without Dart2 semantics a gnarly type error is thrown.
+ shouldFail(0, predicate((bool x) => x, "bool value is true"),
+ endsWith("not an <Instance of \'bool\'>"));
+ }
});
});
-
- test("Feature Matcher", () {
- var w = new Widget();
- w.price = 10;
- shouldPass(w, new HasPrice(10));
- shouldPass(w, new HasPrice(greaterThan(0)));
- shouldFail(
- w,
- new HasPrice(greaterThan(10)),
- "Expected: Widget with a price that is a value greater than <10> "
- "Actual: <Instance of 'Widget'> "
- "Which: has price with value <10> which is not "
- "a value greater than <10>");
- });
-
- test("Custom Matcher Exception", () {
- shouldFail(
- "a",
- new BadCustomMatcher(),
- allOf([
- contains("Expected: feature {1: 'a'} "),
- contains("Actual: 'a' "),
- contains("Which: threw 'Exception: bang' "),
- ]));
- });
}
diff --git a/packages/matcher/test/custom_matcher_test.dart b/packages/matcher/test/custom_matcher_test.dart
new file mode 100644
index 0000000..53f035b
--- /dev/null
+++ b/packages/matcher/test/custom_matcher_test.dart
@@ -0,0 +1,45 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:matcher/matcher.dart';
+import 'package:test/test.dart' show test;
+
+import 'test_utils.dart';
+
+class _BadCustomMatcher extends CustomMatcher {
+ _BadCustomMatcher() : super("feature", "description", {1: "a"});
+ Object featureValueOf(actual) => throw new Exception("bang");
+}
+
+class _HasPrice extends CustomMatcher {
+ _HasPrice(matcher) : super("Widget with a price that is", "price", matcher);
+ Object featureValueOf(actual) => actual.price;
+}
+
+void main() {
+ test("Feature Matcher", () {
+ var w = new Widget();
+ w.price = 10;
+ shouldPass(w, new _HasPrice(10));
+ shouldPass(w, new _HasPrice(greaterThan(0)));
+ shouldFail(
+ w,
+ new _HasPrice(greaterThan(10)),
+ "Expected: Widget with a price that is a value greater than <10> "
+ "Actual: <Instance of 'Widget'> "
+ "Which: has price with value <10> which is not "
+ "a value greater than <10>");
+ });
+
+ test("Custom Matcher Exception", () {
+ shouldFail(
+ "a",
+ new _BadCustomMatcher(),
+ allOf([
+ contains("Expected: feature {1: 'a'} "),
+ contains("Actual: 'a' "),
+ contains("Which: threw 'Exception: bang' "),
+ ]));
+ });
+}
diff --git a/packages/matcher/test/having_test.dart b/packages/matcher/test/having_test.dart
new file mode 100644
index 0000000..31cf7ac
--- /dev/null
+++ b/packages/matcher/test/having_test.dart
@@ -0,0 +1,91 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:matcher/matcher.dart';
+import 'package:test/test.dart' show test, expect, throwsA, group;
+
+import 'test_utils.dart';
+
+void main() {
+ test('success', () {
+ shouldPass(new RangeError('details'), _rangeMatcher);
+ });
+
+ test('failure', () {
+ shouldFail(
+ new RangeError.range(-1, 1, 10),
+ _rangeMatcher,
+ "Expected: <Instance of 'RangeError'> with "
+ "`message`: contains 'details' and `start`: null and `end`: null "
+ 'Actual: RangeError:<RangeError: '
+ 'Invalid value: Not in range 1..10, inclusive: -1> '
+ "Which: has `message` with value 'Invalid value'");
+ });
+
+ // This code is used in the [TypeMatcher] doc comments.
+ test('integaration and example', () {
+ void shouldThrowRangeError(int value) {
+ throw new RangeError.range(value, 10, 20);
+ }
+
+ expect(
+ () => shouldThrowRangeError(5),
+ throwsA(const TypeMatcher<RangeError>()
+ .having((e) => e.start, 'start', greaterThanOrEqualTo(10))
+ .having((e) => e.end, 'end', lessThanOrEqualTo(20))));
+
+ expect(
+ () => shouldThrowRangeError(5),
+ throwsA(isRangeError
+ .having((e) => e.start, 'start', greaterThanOrEqualTo(10))
+ .having((e) => e.end, 'end', lessThanOrEqualTo(20))));
+ });
+
+ group('CustomMater copy', () {
+ test("Feature Matcher", () {
+ var w = new Widget();
+ w.price = 10;
+ shouldPass(w, _hasPrice(10));
+ shouldPass(w, _hasPrice(greaterThan(0)));
+ shouldFail(
+ w,
+ _hasPrice(greaterThan(10)),
+ "Expected: <Instance of 'Widget'> with `price`: a value greater than <10> "
+ "Actual: <Instance of 'Widget'> "
+ "Which: has `price` with value <10> which is not "
+ "a value greater than <10>");
+ });
+
+ test("Custom Matcher Exception", () {
+ shouldFail(
+ 'a',
+ _badCustomMatcher(),
+ allOf([
+ contains(
+ "Expected: <Instance of 'Widget'> with `feature`: {1: 'a'} "),
+ contains("Actual: 'a'"),
+ ]));
+ shouldFail(
+ new Widget(),
+ _badCustomMatcher(),
+ allOf([
+ contains(
+ "Expected: <Instance of 'Widget'> with `feature`: {1: 'a'} "),
+ contains("Actual: <Instance of 'Widget'> "),
+ contains("Which: threw 'Exception: bang' "),
+ ]));
+ });
+ });
+}
+
+final _rangeMatcher = isRangeError
+ .having((e) => e.message, 'message', contains('details'))
+ .having((e) => e.start, 'start', isNull)
+ .having((e) => e.end, 'end', isNull);
+
+Matcher _hasPrice(matcher) =>
+ const TypeMatcher<Widget>().having((e) => e.price, 'price', matcher);
+
+Matcher _badCustomMatcher() => const TypeMatcher<Widget>()
+ .having((e) => throw new Exception("bang"), 'feature', {1: "a"});
diff --git a/packages/matcher/test/iterable_matchers_test.dart b/packages/matcher/test/iterable_matchers_test.dart
index e8a8df8..7c28338 100644
--- a/packages/matcher/test/iterable_matchers_test.dart
+++ b/packages/matcher/test/iterable_matchers_test.dart
@@ -26,6 +26,9 @@
contains(0),
"Expected: contains <0> "
"Actual: [1, 2]");
+
+ shouldFail(
+ 'String', contains(42), "Expected: contains <42> Actual: 'String'");
});
test('equals with matcher element', () {
@@ -40,9 +43,22 @@
});
test('isIn', () {
- var d = [1, 2];
- shouldPass(1, isIn(d));
- shouldFail(0, isIn(d), "Expected: is in [1, 2] Actual: <0>");
+ // Iterable
+ shouldPass(1, isIn([1, 2]));
+ shouldFail(0, isIn([1, 2]), "Expected: is in [1, 2] Actual: <0>");
+
+ // Map
+ shouldPass(1, isIn({1: null}));
+ shouldFail(0, isIn({1: null}), "Expected: is in {1: null} Actual: <0>");
+
+ // String
+ shouldPass('42', isIn('1421'));
+ shouldFail('42', isIn('41'), "Expected: is in '41' Actual: '42'");
+ shouldFail(
+ 0, isIn('a string'), endsWith('not an <Instance of \'Pattern\'>'));
+
+ // Invalid arg
+ expect(() => isIn(42), throwsArgumentError);
});
test('everyElement', () {
@@ -55,6 +71,8 @@
"Actual: [1, 2] "
"Which: has value <2> which doesn't match <1> at index 1");
shouldPass(e, everyElement(1));
+ shouldFail('not iterable', everyElement(1),
+ endsWith('not an <Instance of \'Iterable\'>'));
});
test('nested everyElement', () {
@@ -109,6 +127,8 @@
shouldPass(d, anyElement(2));
shouldFail(
e, anyElement(2), "Expected: some element <2> Actual: [1, 1, 1]");
+ shouldFail('not an iterable', anyElement(2),
+ endsWith('not an <Instance of \'Iterable\'>'));
});
test('orderedEquals', () {
@@ -121,6 +141,8 @@
"Expected: equals [2, 1] ordered "
"Actual: [1, 2] "
"Which: was <1> instead of <2> at location [0]");
+ shouldFail('not an iterable', orderedEquals([1]),
+ endsWith('not an <Instance of \'Iterable\'>'));
});
test('unorderedEquals', () {
@@ -151,6 +173,8 @@
"Actual: [1, 2] "
"Which: has no match for <3> at index 0"
" along with 1 other unmatched");
+ shouldFail('not an iterable', unorderedEquals([1]),
+ endsWith('not an <Instance of \'Iterable\'>'));
});
test('unorderedMatches', () {
@@ -197,6 +221,8 @@
"<0>] unordered "
"Actual: [1, 2] "
"Which: has no match for a value greater than <3> at index 0");
+ shouldFail('not an iterable', unorderedMatches([greaterThan(1)]),
+ endsWith('not an <Instance of \'Iterable\'>'));
});
test('containsAll', () {
@@ -216,7 +242,7 @@
containsAll([1]),
"Expected: contains all of [1] "
"Actual: <1> "
- "Which: not iterable");
+ "Which: not an <Instance of \'Iterable\'>");
shouldFail(
[-1, 2],
containsAll([greaterThan(0), greaterThan(1)]),
@@ -224,6 +250,8 @@
"<a value greater than <1>>] "
"Actual: [-1, 2] "
"Which: has no match for a value greater than <1> at index 1");
+ shouldFail('not an iterable', containsAll([1, 2, 3]),
+ endsWith('not an <Instance of \'Iterable\'>'));
});
test('containsAllInOrder', () {
@@ -257,7 +285,7 @@
containsAllInOrder([1]),
"Expected: contains in order([1]) "
"Actual: <1> "
- "Which: not an iterable");
+ "Which: not an <Instance of \'Iterable\'>");
});
test('pairwise compare', () {
@@ -266,20 +294,21 @@
var e = [1, 4, 9];
shouldFail(
'x',
- pairwiseCompare(e, (e, a) => a <= e, "less than or equal"),
+ pairwiseCompare(e, (int e, int a) => a <= e, "less than or equal"),
"Expected: pairwise less than or equal [1, 4, 9] "
"Actual: 'x' "
- "Which: is not an Iterable");
+ "Which: not an <Instance of \'Iterable\'>");
shouldFail(
c,
- pairwiseCompare(e, (e, a) => a <= e, "less than or equal"),
+ pairwiseCompare(e, (int e, int a) => a <= e, "less than or equal"),
"Expected: pairwise less than or equal [1, 4, 9] "
"Actual: [1, 2] "
"Which: has length 2 instead of 3");
- shouldPass(d, pairwiseCompare(e, (e, a) => a <= e, "less than or equal"));
+ shouldPass(
+ d, pairwiseCompare(e, (int e, int a) => a <= e, "less than or equal"));
shouldFail(
d,
- pairwiseCompare(e, (e, a) => a < e, "less than"),
+ pairwiseCompare(e, (int e, int a) => a < e, "less than"),
"Expected: pairwise less than [1, 4, 9] "
"Actual: [1, 2, 3] "
"Which: has <1> which is not less than <1> at index 0");
@@ -290,6 +319,10 @@
"Expected: pairwise double [1, 4, 9] "
"Actual: [1, 2, 3] "
"Which: has <1> which is not double <1> at index 0");
+ shouldFail(
+ 'not an iterable',
+ pairwiseCompare(e, (e, a) => a + a == e, "double"),
+ endsWith('not an <Instance of \'Iterable\'>'));
});
test('isEmpty', () {
diff --git a/packages/matcher/test/mirror_matchers_test.dart b/packages/matcher/test/mirror_matchers_test.dart
index bbd80a1..e9f88b4 100644
--- a/packages/matcher/test/mirror_matchers_test.dart
+++ b/packages/matcher/test/mirror_matchers_test.dart
@@ -11,9 +11,9 @@
class C {
var instanceField = 1;
- get instanceGetter => 2;
+ int get instanceGetter => 2;
static var staticField = 3;
- static get staticGetter => 4;
+ static int get staticGetter => 4;
}
void main() {
diff --git a/packages/matcher/test/numeric_matchers_test.dart b/packages/matcher/test/numeric_matchers_test.dart
index 9a365d8..a85f13b 100644
--- a/packages/matcher/test/numeric_matchers_test.dart
+++ b/packages/matcher/test/numeric_matchers_test.dart
@@ -24,6 +24,8 @@
"Expected: a numeric value within <1> of <0> "
"Actual: <-1.001> "
"Which: differs by <1.001>");
+ shouldFail(
+ 'not a num', closeTo(0, 1), endsWith('not an <Instance of \'num\'>'));
});
test('inInclusiveRange', () {
@@ -40,6 +42,8 @@
inInclusiveRange(0, 2),
"Expected: be in range from 0 (inclusive) to 2 (inclusive) "
"Actual: <3>");
+ shouldFail('not a num', inInclusiveRange(0, 1),
+ endsWith('not an <Instance of \'num\'>'));
});
test('inExclusiveRange', () {
@@ -54,6 +58,8 @@
inExclusiveRange(0, 2),
"Expected: be in range from 0 (exclusive) to 2 (exclusive) "
"Actual: <2>");
+ shouldFail('not a num', inExclusiveRange(0, 1),
+ endsWith('not an <Instance of \'num\'>'));
});
test('inOpenClosedRange', () {
@@ -64,6 +70,8 @@
"Actual: <0>");
shouldPass(1, inOpenClosedRange(0, 2));
shouldPass(2, inOpenClosedRange(0, 2));
+ shouldFail('not a num', inOpenClosedRange(0, 1),
+ endsWith('not an <Instance of \'num\'>'));
});
test('inClosedOpenRange', () {
@@ -74,5 +82,7 @@
inClosedOpenRange(0, 2),
"Expected: be in range from 0 (inclusive) to 2 (exclusive) "
"Actual: <2>");
+ shouldFail('not a num', inClosedOpenRange(0, 1),
+ endsWith('not an <Instance of \'num\'>'));
});
}
diff --git a/packages/matcher/test/pretty_print_test.dart b/packages/matcher/test/pretty_print_test.dart
index c809df0..59a5950 100644
--- a/packages/matcher/test/pretty_print_test.dart
+++ b/packages/matcher/test/pretty_print_test.dart
@@ -8,6 +8,8 @@
import 'package:matcher/src/pretty_print.dart';
import 'package:test/test.dart' show group, test, expect;
+import 'test_utils.dart';
+
class DefaultToString {}
class CustomToString {
@@ -244,12 +246,19 @@
group('with an iterable', () {
test("that's not a list", () {
- expect(prettyPrint([1, 2, 3, 4].map((n) => n * 2)),
- equals("MappedListIterable:[2, 4, 6, 8]"));
+ expect(
+ prettyPrint([1, 2, 3, 4].map((n) => n * 2)),
+ equals(isDart2
+ ? "MappedListIterable<int, int>:[2, 4, 6, 8]"
+ : "MappedListIterable:[2, 4, 6, 8]"));
});
test("that's not a list and has a private name", () {
expect(prettyPrint(new _PrivateNameIterable()), equals("?:[1, 2, 3]"));
});
});
+
+ test('Type', () {
+ expect(prettyPrint(''.runtimeType), 'Type:<String>');
+ });
}
diff --git a/packages/matcher/test/string_matchers_test.dart b/packages/matcher/test/string_matchers_test.dart
index 28d085c..834df8c 100644
--- a/packages/matcher/test/string_matchers_test.dart
+++ b/packages/matcher/test/string_matchers_test.dart
@@ -55,6 +55,8 @@
shouldPass('hello', equalsIgnoringCase('HELLO'));
shouldFail('hi', equalsIgnoringCase('HELLO'),
"Expected: 'HELLO' ignoring case Actual: 'hi'");
+ shouldFail(42, equalsIgnoringCase('HELLO'),
+ endsWith('not an <Instance of \'String\'>'));
});
test('equalsIgnoringWhitespace', () {
@@ -65,6 +67,8 @@
"Expected: 'hello world' ignoring whitespace "
"Actual: ' helloworld ' "
"Which: is 'helloworld' with whitespace compressed");
+ shouldFail(42, equalsIgnoringWhitespace('HELLO'),
+ endsWith('not an <Instance of \'String\'>'));
});
test('startsWith', () {
@@ -76,6 +80,8 @@
startsWith('hello '),
"Expected: a string starting with 'hello ' "
"Actual: 'hello'");
+ shouldFail(
+ 42, startsWith('hello '), endsWith('not an <Instance of \'String\'>'));
});
test('endsWith', () {
@@ -87,6 +93,8 @@
endsWith(' hello'),
"Expected: a string ending with ' hello' "
"Actual: 'hello'");
+ shouldFail(
+ 42, startsWith('hello '), endsWith('not an <Instance of \'String\'>'));
});
test('contains', () {
@@ -124,5 +132,7 @@
shouldPass('c0d', matches(new RegExp('[a-z][0-9][a-z]')));
shouldFail('cOd', matches('[a-z][0-9][a-z]'),
"Expected: match '[a-z][0-9][a-z]' Actual: 'cOd'");
+ shouldFail(42, matches('[a-z][0-9][a-z]'),
+ endsWith('not an <Instance of \'String\'>'));
});
}
diff --git a/packages/matcher/test/test_utils.dart b/packages/matcher/test/test_utils.dart
index a0dad48..22ad681 100644
--- a/packages/matcher/test/test_utils.dart
+++ b/packages/matcher/test/test_utils.dart
@@ -4,6 +4,11 @@
import 'package:test/test.dart';
+final bool isDart2 = () {
+ Type checkType<T>() => T;
+ return checkType<String>() == String;
+}();
+
void shouldFail(value, Matcher matcher, expected) {
var failed = false;
try {
@@ -11,12 +16,12 @@
} on TestFailure catch (err) {
failed = true;
- var _errorString = err.message;
+ var errorString = err.message;
if (expected is String) {
- expect(_errorString, equalsIgnoringWhitespace(expected));
+ expect(errorString, equalsIgnoringWhitespace(expected));
} else {
- expect(_errorString.replaceAll('\n', ''), expected);
+ expect(errorString.replaceAll('\n', ''), expected);
}
}
@@ -27,20 +32,15 @@
expect(value, matcher);
}
-doesNotThrow() {}
-doesThrow() {
- throw 'X';
+void doesNotThrow() {}
+void doesThrow() {
+ throw new StateError('X');
}
class Widget {
int price;
}
-class HasPrice extends CustomMatcher {
- HasPrice(matcher) : super("Widget with a price that is", "price", matcher);
- featureValueOf(actual) => actual.price;
-}
-
class SimpleIterable extends Iterable<int> {
final int count;
diff --git a/packages/matcher/test/type_matcher_test.dart b/packages/matcher/test/type_matcher_test.dart
new file mode 100644
index 0000000..e20294f
--- /dev/null
+++ b/packages/matcher/test/type_matcher_test.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:matcher/matcher.dart';
+import 'package:test/test.dart' show test, group;
+
+import 'test_utils.dart';
+
+void main() {
+ _test('Map', isMap, {});
+ _test('List', isList, []);
+ _test('ArgumentError', isArgumentError, new ArgumentError());
+ _test('Exception', isException, const FormatException());
+ _test('FormatException', isFormatException, const FormatException());
+ _test('StateError', isStateError, new StateError('oops'));
+ _test('RangeError', isRangeError, new RangeError('oops'));
+ _test('UnimplementedError', isUnimplementedError,
+ new UnimplementedError('oops'));
+ _test('UnsupportedError', isUnsupportedError, new UnsupportedError('oops'));
+ _test('ConcurrentModificationError', isConcurrentModificationError,
+ new ConcurrentModificationError());
+ _test('CyclicInitializationError', isCyclicInitializationError,
+ new CyclicInitializationError());
+ _test('NoSuchMethodError', isNoSuchMethodError, null);
+ _test('NullThrownError', isNullThrownError, new NullThrownError());
+
+ group('custom `TypeMatcher`', () {
+ // ignore: deprecated_member_use
+ _test('String', const isInstanceOf<String>(), 'hello');
+ _test('String', const _StringMatcher(), 'hello');
+ _test('String', const TypeMatcher<String>(), 'hello');
+ });
+}
+
+// TODO: drop `name` and use a type argument – once Dart2 semantics are enabled
+void _test(String name, Matcher typeMatcher, Object matchingType) {
+ group('for `$name`', () {
+ if (matchingType != null) {
+ test('succeeds', () {
+ shouldPass(matchingType, typeMatcher);
+ });
+ }
+
+ test('fails', () {
+ shouldFail(const TestType(), typeMatcher,
+ "Expected: <Instance of '$name'> Actual: <Instance of 'TestType'>");
+ });
+ });
+}
+
+// Validate that existing implementations continue to work.
+class _StringMatcher extends TypeMatcher {
+ const _StringMatcher() : super(
+ // ignore: deprecated_member_use
+ 'String');
+
+ bool matches(item, Map matchState) => item is String;
+}
+
+class TestType {
+ const TestType();
+}
diff --git a/packages/observable/.analysis_options b/packages/observable/.analysis_options
deleted file mode 100644
index dbb6342..0000000
--- a/packages/observable/.analysis_options
+++ /dev/null
@@ -1,49 +0,0 @@
-analyzer:
- strong-mode: true
-linter:
- rules:
- #- always_declare_return_types
- #- always_specify_types
- #- annotate_overrides
- #- avoid_as
- - avoid_empty_else
- - avoid_init_to_null
- - avoid_return_types_on_setters
- - await_only_futures
- - camel_case_types
- - cancel_subscriptions
- #- close_sinks
- #- comment_references
- - constant_identifier_names
- - control_flow_in_finally
- - empty_catches
- - empty_constructor_bodies
- - empty_statements
- - hash_and_equals
- - implementation_imports
- - iterable_contains_unrelated_type
- - library_names
- - library_prefixes
- - list_remove_unrelated_type
- - non_constant_identifier_names
- - one_member_abstracts
- - only_throw_errors
- - overridden_fields
- - package_api_docs
- - package_names
- - package_prefixed_library_names
- - prefer_is_not_empty
- #- public_member_api_docs
- #- slash_for_doc_comments
- #- sort_constructors_first
- #- sort_unnamed_constructors_first
- - super_goes_last
- - test_types_in_equals
- - throw_in_finally
- #- type_annotate_public_apis
- - type_init_formals
- #- unawaited_futures
- - unnecessary_brace_in_string_interp
- - unnecessary_getters_setters
- - unrelated_type_equality_checks
- - valid_regexps
diff --git a/packages/observable/.gitignore b/packages/observable/.gitignore
index 96bc6bb..e43b40f 100644
--- a/packages/observable/.gitignore
+++ b/packages/observable/.gitignore
@@ -1,4 +1,5 @@
# Files and directories created by pub
+.dart_tool/
.packages
.pub/
packages
diff --git a/packages/observable/.travis.yml b/packages/observable/.travis.yml
new file mode 100644
index 0000000..a784107
--- /dev/null
+++ b/packages/observable/.travis.yml
@@ -0,0 +1,53 @@
+language: dart
+
+# Gives more resources on Travis (8GB Ram, 2 CPUs).
+# Do not remove without verifying w/ Travis.
+sudo: required
+addons:
+ chrome: stable
+
+# Build stages: https://docs.travis-ci.com/user/build-stages/.
+stages:
+ - presubmit
+ - build
+ - testing
+
+# 1. Run dartfmt, dartanalyzer, pub run test (VM).
+# 2. Then run a build.
+# 3. Then run tests compiled via dartdevc and dart2js.
+jobs:
+ include:
+ - stage: presubmit
+ script: ./tool/travis.sh dartfmt
+ dart: dev
+ - stage: presubmit
+ script: ./tool/travis.sh dartanalyzer
+ dart: dev
+ - stage: build
+ script: ./tool/travis.sh dartdevc_build
+ dart: dev
+ - stage: testing
+ script: ./tool/travis.sh vm_test
+ dart: dev
+ - stage: testing
+ script: ./tool/travis.sh dartdevc_test
+ dart: dev
+ - stage: testing
+ script: ./tool/travis.sh dart2js_test
+ dart: dev
+
+# Only building master means that we don't run two builds for each pull request.
+branches:
+ only: [master]
+
+# Incremental pub cache and builds.
+cache:
+ directories:
+ - $HOME/.pub-cache
+ - .dart_tool
+
+# Necessary for Chrome and Firefox to run
+before_install:
+ - export DISPLAY=:99.0
+ - sh -e /etc/init.d/xvfb start
+ - "t=0; until (xdpyinfo -display :99 &> /dev/null || test $t -gt 10); do sleep 1; let t=$t+1; done"
diff --git a/packages/observable/CHANGELOG.md b/packages/observable/CHANGELOG.md
index aca61a5..fb9e89d 100644
--- a/packages/observable/CHANGELOG.md
+++ b/packages/observable/CHANGELOG.md
@@ -1,3 +1,247 @@
+## 0.22.1+3
+
+Update implementations of the `cast()` and the deprecated `retype()` methods.
+* The `retype()` method on List and Map is deprecated and will be removed.
+* The `cast()` method should do what the `retype()` method did.
+
+## 0.22.1+2
+
+* Widen dependency on quiver to include v0.29.
+
+## 0.22.1+1
+
+* Fixes for Dart2 runtime type errors.
+
+## 0.22.1
+
+* Added `ObservableList.castFrom`, similar to `List.castFrom`.
+
+* Changed `ObservableList`'s `cast` and `retype` function to create a forwarding
+ instance of `ObservableList` instead of an instance of `List`.
+
+## 0.22.0
+
+* Added `ObservableMap.castFrom`, similar to `Map.castFrom`.
+
+* Fixed a bug where `ObservableMap`'s `cast` and `retype` function would create
+ a new empty instance instead of a forwarding instance.
+
+## 0.21.3
+
+* Support Dart 2 collection methods where previously threw `UnimplementedError`.
+
+## 0.21.2
+
+* Fix `toObservable(deep: false)` to be shallow again.
+* Remove use of `Maps`, for better compatibility with Dart 2.
+
+## 0.21.1
+
+* Updated one test to comply with Dart 2 voidness semantics.
+* Fix Dart 2 runtime cast failure in `toObservable()`.
+* Loosen `ObservableList.from()` to take `Iterable`, not `Iterable<T>`. This
+ matches `List.from()` and avoids some unnecessary cast failures.
+
+## 0.21.0
+
+### Breaking Changes
+
+Version 0.21.0 reverts to version 0.17.0+1 with fixes to support Dart 2.
+Versions 0.18, 0.19, and 0.20 were not used by the package authors and
+effectively unsupported. This resolves the fork that happened at version 0.18
+and development can now be supported by the authors.
+
+#### Reverted Changes
+
+(From 0.20.1)
+* Revert add `Observable<List|Set|Map>.unmodifiable` for immutable collections
+* Revert add `Observable<List|Set|Map>.EMPTY` for empty immutable collections
+ * This can be used as an optimization for libraries that always need to return
+ an observable collection, but don't want to allocate a new instance to
+ represent an empty immutable.
+
+(From 0.20.0)
+* Revert add `ObservableSet`, `SetChangeRecord`, and `SetDiffer`
+
+(From 0.19.0)
+* Revert refactor and deprecate `ObservableMap`-specific API
+ * `ObservableMap` no longer emits `#keys` and `#values` change records
+ * `ObservableMap.spy` is deprecated, becomes `.delegate` instead
+* Revert Potentially breaking: `ObservableMap` may no longer be extended
+
+Revert considered deprecated to be notified of `length` changes.
+
+(From 0.18.0)
+* Revert refactor and deprecate `ObservableList`-specific API
+ * `ObservableList.applyChangeRecords`
+ * `ObservableList.calculateChangeRecords`
+ * `ObservableList.withLength`
+ * `ObservableList.deliverListChanges`
+ * `ObservableList.discardListChanges`
+ * `ObservableList.hasListChanges`
+ * `ObservableList.listChanges`
+ * `ObservableList.notifyListChange`
+* Revert potentially breaking: `ObservableList` may no longer be extended
+
+Revert considered deprecated to be notified of `length`, `isEmpty` and
+`isNotEmpty` `PropertyChangeRecord`s on `ObservableList`
+
+#### Changes Applied on top of version 0.17.0+1
+(With internal change numbers)
+
+* Flip deliverChanges from `@protected` to `@visibleForTesting`. cl/147029982
+* Fix a typing bug in observable when running with DDC: `ChangeRecord.NONE`
+ creates a `List<ChangeRecord>`, while the call sites expect a
+ `List<ListChangeRecord>` or `List<MapChangeRecord>`, respectively.
+ cl/155201160
+* Fix `Observable._isNotGeneric` check. cl/162282107
+* Fix issue with type in deliverChanges. cl/162493576
+* Stop using the comment syntax for generics. cl/163224019
+* Fix ListChangeRecord's added getter. Add checks for the added and removed
+ getters in listChangeTests. cl/169261086.
+* Migrate observable to real generic method syntax. cl/170239122
+* Fix only USES_DYNAMIC_AS_BOTTOM error in observable. cl/179946618
+* Cherry pick https://github.com/dart-lang/observable/pull/46.
+* Stub out Dart 2 core lib changes in ObservableMap.
+* Removed `Observable{List|Map}.NONE` (not Dart2 compatible).
+* Fix issue with type in `ObservableList._notifyListChange`. cl/182284033
+
+## 0.20.4+3
+
+* Support the latest release of `pkg/quiver` (0.27).
+
+## 0.20.4+2
+
+* Support the latest release of `pkg/quiver` (0.26).
+* Bug fix: Some minor type fixes for strict runtimes (and Dart 2.0), namely:
+ * PropertyChangeNotifier merely `extends
+ ChangeNotifier` rather than `extends ChangeNotifier<PropertyChangeRecord>`.
+ * Introduce new `ListChangeRecord.NONE` and `MapChangeRecord.NONE`.
+
+## 0.20.4+1
+
+* Support the latest release of `pkg/quiver` (0.25).
+
+## 0.20.4
+
+* Bug fix: Additional fix around `ObservableList.listChanges`
+
+## 0.20.3
+
+* Bug fix: Avoid emitting an empty list via `ObservableList.listChanges`
+
+## 0.20.2
+
+* Bug fix: Avoid emitting a no-op `MapChangeRecord`
+* Bug fix: Restore `ObservableList.discardListChanges` functionality
+
+## 0.20.1
+
+* Add `Observable<List|Set|Map>.unmodifiable` for immutable collections
+* Add `Observable<List|Set|Map>.EMPTY` for empty immutable collections
+ * This can be used as an optimization for libraries that always
+ need to return an observable collection, but don't want to
+ allocate a new instance to represent an empty immutable.
+
+## 0.20.0
+
+* Add `ObservableSet`, `SetChangeRecord`, and `SetDiffer`
+
+## 0.19.0
+
+* Refactor and deprecate `ObservableMap`-specific API
+ * `ObservableMap` no longer emits `#keys` and `#values` change records
+ * `ObservableMap.spy` is deprecated, becomes `.delegate` instead
+* Potentially breaking: `ObservableMap` may no longer be extended
+
+It is also considered deprecated to be notified of `length` changes.
+
+## 0.18.1
+
+* Bug fix: Do not throw when `Observable<T>.notifyChange` is used
+
+## 0.18.0
+
+* Refactor and deprecate `ObservableList`-specific API
+ * `ObservableList.applyChangeRecords`
+ * `ObservableList.calculateChangeRecords`
+ * `ObservableList.withLength`
+ * `ObservableList.deliverListChanges`
+ * `ObservableList.discardListChanges`
+ * `ObservableList.hasListChanges`
+ * `ObservableList.listChanges`
+ * `ObservableList.notifyListChange`
+* Potentially breaking: `ObservableList` may no longer be extended
+
+It is also considered deprecated to be notified of `length`, `isEmpty`
+and `isNotEmpty` `PropertyChangeRecord`s on `ObservableList` - in a
+future release `ObservableList.changes` will be
+`Stream<List<ListChangeRecord>>`.
+
+## 0.17.0+1
+
+* Revert `PropertyChangeMixin`, which does not work in dart2js
+
+## 0.17.0
+
+This is a larger change with a goal of no runtime changes for current
+customers, but in the future `Observable` will [become][issue_10] a very
+lightweight interface, i.e.:
+
+```dart
+abstract class Observable<C extends ChangeRecord> {
+ Stream<List<C>> get changes;
+}
+```
+
+[issue_10]: https://github.com/dart-lang/observable/issues/10
+
+* Started deprecating the wide `Observable` interface
+ * `ChangeNotifier` should be used as a base class for these methods:
+ * `Observable.observed`
+ * `Observable.unobserved`
+ * `Observable.hasObservers`
+ * `Observable.deliverChanges`
+ * `Observable.notifyChange`
+ * `PropertyChangeNotifier` should be used for these methods:
+ * `Observable.notifyPropertyChange`
+ * Temporarily, `Observable` _uses_ `ChangeNotifier`
+ * Existing users of anything but `implements Observable` should
+ move to implementing or extending `ChangeNotifier`. In a
+ future release `Observable` will reduce API surface down to
+ an abstract `Stream<List<C>> get changes`.
+* Added the `ChangeNotifier` and `PropertyChangeNotifier` classes
+ * Can be used to implement `Observable` in a generic manner
+* Observable is now `Observable<C extends ChangeRecord>`
+ * When passing a generic type `C`, `notifyPropertyChange` is illegal
+
+## 0.16.0
+
+* Refactored `MapChangeRecord`
+ * Added equality and hashCode checks
+ * Added `MapChangeRecord.apply` to apply a change record
+* Added `MapDiffer`, which implements `Differ` for a `Map`
+
+## 0.15.0+2
+
+* Fix a bug in `ListDiffer` that caused a `RangeError`
+
+## 0.15.0+1
+
+* Fix analysis errors caused via missing `/*<E>*/` syntax in `0.15.0`
+
+## 0.15.0
+
+* Added the `Differ` interface, as well as `EqualityDiffer`
+* Refactored list diffing into a `ListDiffer`
+* Added concept of `ChangeRecord.ANY` and `ChangeRecord.NONE`
+ * Low-GC ways to expression "something/nothing" changed
+* Refactored `ListChangeRecord`
+ * Added named constructors for common use cases
+ * Added equality and hashCode checks
+ * Added `ListChangeRecord.apply` to apply a change record
+* Added missing `@override` annotations to satisfy `annotate_overrides`
+
## 0.14.0+1
* Add a missing dependency on `pkg/meta`.
diff --git a/packages/observable/PATENTS b/packages/observable/PATENTS
deleted file mode 100644
index 6954196..0000000
--- a/packages/observable/PATENTS
+++ /dev/null
@@ -1,23 +0,0 @@
-Additional IP Rights Grant (Patents)
-
-"This implementation" means the copyrightable works distributed by
-Google as part of the Dart Project.
-
-Google hereby grants to you a perpetual, worldwide, non-exclusive,
-no-charge, royalty-free, irrevocable (except as stated in this
-section) patent license to make, have made, use, offer to sell, sell,
-import, transfer, and otherwise run, modify and propagate the contents
-of this implementation of Dart, where such license applies only to
-those patent claims, both currently owned by Google and acquired in
-the future, licensable by Google that are necessarily infringed by
-this implementation of Dart. This grant does not include claims that
-would be infringed only as a consequence of further modification of
-this implementation. If you or your agent or exclusive licensee
-institute or order or agree to the institution of patent litigation
-against any entity (including a cross-claim or counterclaim in a
-lawsuit) alleging that this implementation of Dart or any code
-incorporated within this implementation of Dart constitutes direct or
-contributory patent infringement, or inducement of patent
-infringement, then any patent rights granted to you under this License
-for this implementation of Dart shall terminate as of the date such
-litigation is filed.
diff --git a/packages/observable/README.md b/packages/observable/README.md
index 3965a6f..bd82499 100644
--- a/packages/observable/README.md
+++ b/packages/observable/README.md
@@ -1,5 +1,20 @@
-Support for marking objects as observable, and getting notifications when those
-objects are mutated.
+[![Build Status](https://travis-ci.org/dart-lang/observable.svg?branch=master)](https://travis-ci.org/dart-lang/observable)
-This library is used to observe changes to observable types. It also
-has helpers to make implementing and using observable objects easy.
+Support for detecting and being notified when an object is mutated.
+
+An observable is a way to be notified of a continuous stream of events over time.
+
+Some suggested uses for this library:
+
+* Observe objects for changes, and log when a change occurs
+* Optimize for observable collections in your own APIs and libraries instead of diffing
+* Implement simple data-binding by listening to streams
+
+You may want to look at the former TC39 proposal [Observe.observe](https://github.com/tc39/proposal-observable), which was deprecated.
+
+### Usage
+
+There are two general ways to detect changes:
+
+* Listen to `Observable.changes` and be notified when an object changes
+* Use `Differ.diff` to determine changes between two objects
diff --git a/packages/observable/analysis_options.yaml b/packages/observable/analysis_options.yaml
new file mode 100644
index 0000000..6f04432
--- /dev/null
+++ b/packages/observable/analysis_options.yaml
@@ -0,0 +1,28 @@
+analyzer:
+ strong-mode: true
+linter:
+ rules:
+ # Errors
+ - avoid_empty_else
+ - control_flow_in_finally
+ - empty_statements
+ - test_types_in_equals
+ - throw_in_finally
+ - valid_regexps
+
+ # Style
+ - annotate_overrides
+ - avoid_init_to_null
+ - avoid_return_types_on_setters
+ - await_only_futures
+ - camel_case_types
+ - comment_references
+ - empty_catches
+ - empty_constructor_bodies
+ - hash_and_equals
+ - library_prefixes
+ - non_constant_identifier_names
+ - prefer_is_not_empty
+ - slash_for_doc_comments
+ - type_init_formals
+ - unrelated_type_equality_checks
diff --git a/packages/observable/dart_test.yaml b/packages/observable/dart_test.yaml
new file mode 100644
index 0000000..d2b9d1d
--- /dev/null
+++ b/packages/observable/dart_test.yaml
@@ -0,0 +1,22 @@
+presets:
+ # When run with -P travis, we have different settings/options.
+ #
+ # 1: We don't use Chrome --headless:
+ # 2: We use --reporter expanded
+ # 3: We skip anything tagged "fails-on-travis".
+ travis:
+ # TODO(https://github.com/dart-lang/test/issues/772)
+ override_platforms:
+ chrome:
+ settings:
+ headless: false
+
+ # Don't run any tests that are tagged ["fails-on-travis"].
+ exclude_tags: "fails-on-travis"
+
+ # https://github.com/dart-lang/test/blob/master/doc/configuration.md#reporter
+ reporter: expanded
+
+platforms:
+ - chrome
+ - vm
diff --git a/packages/observable/lib/observable.dart b/packages/observable/lib/observable.dart
index ce82061..516ff70 100644
--- a/packages/observable/lib/observable.dart
+++ b/packages/observable/lib/observable.dart
@@ -4,10 +4,11 @@
library observable;
-export 'src/change_record.dart';
-export 'src/list_diff.dart' show ListChangeRecord;
+export 'src/change_notifier.dart' show ChangeNotifier, PropertyChangeNotifier;
+export 'src/differs.dart' show Differ, EqualityDiffer, ListDiffer, MapDiffer;
+export 'src/records.dart'
+ show ChangeRecord, ListChangeRecord, MapChangeRecord, PropertyChangeRecord;
export 'src/observable.dart';
export 'src/observable_list.dart';
export 'src/observable_map.dart';
-export 'src/property_change_record.dart';
export 'src/to_observable.dart';
diff --git a/packages/observable/lib/src/change_notifier.dart b/packages/observable/lib/src/change_notifier.dart
new file mode 100644
index 0000000..9464740
--- /dev/null
+++ b/packages/observable/lib/src/change_notifier.dart
@@ -0,0 +1,132 @@
+import 'dart:async';
+
+import 'package:meta/meta.dart';
+
+import 'internal.dart';
+import 'observable.dart';
+import 'records.dart';
+
+/// Supplies [changes] and various hooks to implement [Observable].
+///
+/// May use [notifyChange] to queue a change record; they are asynchronously
+/// delivered at the end of the VM turn.
+///
+/// [ChangeNotifier] may be extended, mixed in, or used as a delegate.
+class ChangeNotifier<C extends ChangeRecord> implements Observable<C> {
+ StreamController<List<C>> _changes;
+
+ bool _scheduled = false;
+ List<C> _queue;
+
+ /// Emits a list of changes when the state of the object changes.
+ ///
+ /// Changes should produced in order, if significant.
+ @override
+ Stream<List<C>> get changes {
+ return (_changes ??= new StreamController<List<C>>.broadcast(
+ sync: true,
+ onListen: observed,
+ onCancel: unobserved,
+ ))
+ .stream;
+ }
+
+ /// May override to be notified when [changes] is first observed.
+ @override
+ @protected
+ @mustCallSuper
+ void observed() {}
+
+ /// May override to be notified when [changes] is no longer observed.
+ @override
+ @protected
+ @mustCallSuper
+ void unobserved() {
+ _changes = _queue = null;
+ }
+
+ /// If [hasObservers], synchronously emits [changes] that have been queued.
+ ///
+ /// Returns `true` if changes were emitted.
+ @override
+ @mustCallSuper
+ bool deliverChanges() {
+ List<ChangeRecord> changes;
+ if (_scheduled && hasObservers) {
+ if (_queue != null) {
+ changes = freezeInDevMode(_queue);
+ _queue = null;
+ } else {
+ changes = ChangeRecord.ANY;
+ }
+ _scheduled = false;
+ _changes.add(changes);
+ }
+ return changes != null;
+ }
+
+ /// Whether [changes] has at least one active listener.
+ ///
+ /// May be used to optimize whether to produce change records.
+ @override
+ bool get hasObservers => _changes?.hasListener == true;
+
+ /// Schedules [change] to be delivered.
+ ///
+ /// If [change] is omitted then [ChangeRecord.ANY] will be sent.
+ ///
+ /// If there are no listeners to [changes], this method does nothing.
+ @override
+ void notifyChange([C change]) {
+ if (!hasObservers) {
+ return;
+ }
+ if (change != null) {
+ (_queue ??= <C>[]).add(change);
+ }
+ if (!_scheduled) {
+ scheduleMicrotask(deliverChanges);
+ _scheduled = true;
+ }
+ }
+
+ @Deprecated('Exists to make migrations off Observable easier')
+ @override
+ @protected
+ T notifyPropertyChange<T>(
+ Symbol field,
+ T oldValue,
+ T newValue,
+ ) {
+ throw new UnsupportedError('Not supported by ChangeNotifier');
+ }
+}
+
+/// Supplies property `changes` and various hooks to implement [Observable].
+///
+/// May use `notifyChange` or `notifyPropertyChange` to queue a property change
+/// record; they are asynchronously delivered at the end of the VM turn.
+///
+/// [PropertyChangeNotifier] may be extended or used as a delegate. To use as
+/// a mixin, instead use with [PropertyChangeMixin]:
+/// with ChangeNotifier<PropertyChangeRecord>, PropertyChangeMixin
+class PropertyChangeNotifier extends ChangeNotifier {
+ @override
+ T notifyPropertyChange<T>(
+ Symbol field,
+ T oldValue,
+ T newValue,
+ ) {
+ if (hasObservers && oldValue != newValue) {
+ notifyChange(
+ new PropertyChangeRecord<T>(
+ this,
+ field,
+ oldValue,
+ newValue,
+ ),
+ );
+ }
+ return newValue;
+ }
+}
diff --git a/packages/observable/lib/src/change_record.dart b/packages/observable/lib/src/change_record.dart
deleted file mode 100644
index b4c4f24..0000000
--- a/packages/observable/lib/src/change_record.dart
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-library observable.src.change_record;
-
-/// Records a change to an [Observable].
-// TODO(jmesserly): remove this type
-abstract class ChangeRecord {}
diff --git a/packages/observable/lib/src/differs.dart b/packages/observable/lib/src/differs.dart
new file mode 100644
index 0000000..40f005e
--- /dev/null
+++ b/packages/observable/lib/src/differs.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observable.src.differs;
+
+import 'dart:math' as math;
+
+import 'package:collection/collection.dart';
+
+import 'records.dart';
+
+import 'internal.dart';
+
+part 'differs/list_differ.dart';
+part 'differs/map_differ.dart';
+
+/// Generic comparisons between two comparable objects.
+abstract class Differ<E> {
+ /// Returns a list of change records between [oldValue] and [newValue].
+ ///
+ /// A return value of an empty [ChangeRecord.NONE] means no changes found.
+ List<ChangeRecord> diff(E oldValue, E newValue);
+}
+
+/// Uses [Equality] to determine a simple [ChangeRecord.ANY] response.
+class EqualityDiffer<E> implements Differ<E> {
+ final Equality<E> _equality;
+
+ const EqualityDiffer([this._equality = const DefaultEquality()]);
+
+ const EqualityDiffer.identity() : this._equality = const IdentityEquality();
+
+ @override
+ List<ChangeRecord> diff(E oldValue, E newValue) {
+ return _equality.equals(oldValue, newValue)
+ ? ChangeRecord.NONE
+ : ChangeRecord.ANY;
+ }
+}
diff --git a/packages/observable/lib/src/differs/list_differ.dart b/packages/observable/lib/src/differs/list_differ.dart
new file mode 100644
index 0000000..c59830e
--- /dev/null
+++ b/packages/observable/lib/src/differs/list_differ.dart
@@ -0,0 +1,469 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of observable.src.differs;
+
+/// Determines differences between two lists, returning [ListChangeRecord]s.
+///
+/// While [ListChangeRecord] has more information and can be replayed they carry
+/// a more significant cost to calculate and create and should only be used when
+/// the details in the record will actually be used.
+///
+/// See also [EqualityDiffer] for a simpler comparison.
+class ListDiffer<E> implements Differ<List<E>> {
+ final Equality<E> _equality;
+
+ const ListDiffer([this._equality = const DefaultEquality()]);
+
+ @override
+ List<ListChangeRecord<E>> diff(List<E> e1, List<E> e2) {
+ return _calcSplices<E>(
+ e2,
+ _equality,
+ 0,
+ e2.length,
+ e1,
+ 0,
+ e1.length,
+ );
+ }
+}
+
+enum _Edit {
+ leave,
+ update,
+ add,
+ delete,
+}
+
+// Note: This function is *based* on the computation of the Levenshtein
+// "edit" distance. The one change is that "updates" are treated as two
+// edits - not one. With List splices, an update is really a delete
+// followed by an add. By retaining this, we optimize for "keeping" the
+// maximum array items in the original array. For example:
+//
+// 'xxxx123' -> '123yyyy'
+//
+// With 1-edit updates, the shortest path would be just to update all seven
+// characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
+// leaves the substring '123' intact.
+List<List<int>> _calcEditDistance<E>(
+ List<E> current,
+ int currentStart,
+ int currentEnd,
+ List<E> old,
+ int oldStart,
+ int oldEnd,
+) {
+ // 'Deletion' columns.
+ final rowCount = oldEnd - oldStart + 1;
+ final columnCount = currentEnd - currentStart + 1;
+ final distances = new List<List<int>>(rowCount);
+
+ // 'Addition' rows. Initialize null column.
+ for (var i = 0; i < rowCount; i++) {
+ distances[i] = new List<int>(columnCount);
+ distances[i][0] = i;
+ }
+
+ // Initialize null row.
+ for (var j = 0; j < columnCount; j++) {
+ distances[0][j] = j;
+ }
+
+ for (var i = 1; i < rowCount; i++) {
+ for (var j = 1; j < columnCount; j++) {
+ if (old[oldStart + i - 1] == current[currentStart + j - 1]) {
+ distances[i][j] = distances[i - 1][j - 1];
+ } else {
+ final north = distances[i - 1][j] + 1;
+ final west = distances[i][j - 1] + 1;
+ distances[i][j] = math.min(north, west);
+ }
+ }
+ }
+
+ return distances;
+}
+
+// This starts at the final weight, and walks "backward" by finding
+// the minimum previous weight recursively until the origin of the weight
+// matrix.
+Iterable<_Edit> _spliceOperationsFromEditDistances(List<List<int>> distances) {
+ var i = distances.length - 1;
+ var j = distances[0].length - 1;
+ var current = distances[i][j];
+ final edits = <_Edit>[];
+ while (i > 0 || j > 0) {
+ if (i == 0) {
+ edits.add(_Edit.add);
+ j--;
+ continue;
+ }
+ if (j == 0) {
+ edits.add(_Edit.delete);
+ i--;
+ continue;
+ }
+ final northWest = distances[i - 1][j - 1];
+ final west = distances[i - 1][j];
+ final north = distances[i][j - 1];
+
+ final min = math.min(math.min(west, north), northWest);
+ if (min == northWest) {
+ if (northWest == current) {
+ edits.add(_Edit.leave);
+ } else {
+ edits.add(_Edit.update);
+ current = northWest;
+ }
+ i--;
+ j--;
+ } else if (min == west) {
+ edits.add(_Edit.delete);
+ i--;
+ current = west;
+ } else {
+ edits.add(_Edit.add);
+ j--;
+ current = north;
+ }
+ }
+
+ return edits.reversed;
+}
+
+int _sharedPrefix<E>(
+ Equality<E> equality,
+ List<E> e1,
+ List<E> e2,
+ int searchLength,
+) {
+ for (var i = 0; i < searchLength; i++) {
+ if (!equality.equals(e1[i], e2[i])) {
+ return i;
+ }
+ }
+ return searchLength;
+}
+
+int _sharedSuffix<E>(
+ Equality<E> equality,
+ List<E> e1,
+ List<E> e2,
+ int searchLength,
+) {
+ var index1 = e1.length;
+ var index2 = e2.length;
+ var count = 0;
+ while (count < searchLength && equality.equals(e1[--index1], e2[--index2])) {
+ count++;
+ }
+ return count;
+}
+
+// Lacking individual splice mutation information, the minimal set of
+// splices can be synthesized given the previous state and final state of an
+// array. The basic approach is to calculate the edit distance matrix and
+// choose the shortest path through it.
+//
+// Complexity: O(l * p)
+// l: The length of the current array
+// p: The length of the old array
+List<ListChangeRecord<E>> _calcSplices<E>(
+ List<E> current,
+ Equality<E> equality,
+ int currentStart,
+ int currentEnd,
+ List<E> old,
+ int oldStart,
+ int oldEnd,
+) {
+ var prefixCount = 0;
+ var suffixCount = 0;
+ final minLength = math.min(currentEnd - currentStart, oldEnd - oldStart);
+ if (currentStart == 0 && oldStart == 0) {
+ prefixCount = _sharedPrefix(
+ equality,
+ current,
+ old,
+ minLength,
+ );
+ }
+ if (currentEnd == current.length && oldEnd == old.length) {
+ suffixCount = _sharedSuffix(
+ equality,
+ current,
+ old,
+ minLength - prefixCount,
+ );
+ }
+
+ currentStart += prefixCount;
+ oldStart += prefixCount;
+ currentEnd -= suffixCount;
+ oldEnd -= suffixCount;
+
+ if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) {
+ return const [];
+ }
+
+ if (currentStart == currentEnd) {
+ final spliceRemoved = old.sublist(oldStart, oldEnd);
+ return [
+ new ListChangeRecord<E>.remove(
+ current,
+ currentStart,
+ spliceRemoved,
+ ),
+ ];
+ }
+ if (oldStart == oldEnd) {
+ return [
+ new ListChangeRecord<E>.add(
+ current,
+ currentStart,
+ currentEnd - currentStart,
+ ),
+ ];
+ }
+
+ final ops = _spliceOperationsFromEditDistances(
+ _calcEditDistance(
+ current,
+ currentStart,
+ currentEnd,
+ old,
+ oldStart,
+ oldEnd,
+ ),
+ );
+
+ var spliceIndex = -1;
+ var spliceRemovals = <E>[];
+ var spliceAddedCount = 0;
+
+ bool hasSplice() => spliceIndex != -1;
+ void resetSplice() {
+ spliceIndex = -1;
+ spliceRemovals = <E>[];
+ spliceAddedCount = 0;
+ }
+
+ var splices = <ListChangeRecord<E>>[];
+
+ var index = currentStart;
+ var oldIndex = oldStart;
+ for (final op in ops) {
+ switch (op) {
+ case _Edit.leave:
+ if (hasSplice()) {
+ splices.add(new ListChangeRecord<E>(
+ current,
+ spliceIndex,
+ removed: spliceRemovals,
+ addedCount: spliceAddedCount,
+ ));
+ resetSplice();
+ }
+ index++;
+ oldIndex++;
+ break;
+ case _Edit.update:
+ if (!hasSplice()) {
+ spliceIndex = index;
+ }
+ spliceAddedCount++;
+ index++;
+ spliceRemovals.add(old[oldIndex]);
+ oldIndex++;
+ break;
+ case _Edit.add:
+ if (!hasSplice()) {
+ spliceIndex = index;
+ }
+ spliceAddedCount++;
+ index++;
+ break;
+ case _Edit.delete:
+ if (!hasSplice()) {
+ spliceIndex = index;
+ }
+ spliceRemovals.add(old[oldIndex]);
+ oldIndex++;
+ break;
+ }
+ }
+ if (hasSplice()) {
+ splices.add(new ListChangeRecord<E>(
+ current,
+ spliceIndex,
+ removed: spliceRemovals,
+ addedCount: spliceAddedCount,
+ ));
+ }
+ assert(() {
+ splices = new List<ListChangeRecord<E>>.unmodifiable(splices);
+ return true;
+ }());
+ return splices;
+}
+
+int _intersect(int start1, int end1, int start2, int end2) {
+ return math.min(end1, end2) - math.max(start1, start2);
+}
+
+void _mergeSplices<E>(
+ List<ListChangeRecord<E>> splices,
+ ListChangeRecord<E> record,
+) {
+ var spliceIndex = record.index;
+ var spliceRemoved = record.removed;
+ var spliceAdded = record.addedCount;
+
+ var inserted = false;
+ var insertionOffset = 0;
+
+ // I think the way this works is:
+ // - the loop finds where the merge should happen
+ // - it applies the merge in a particular splice
+ // - then continues and updates the subsequent splices with any offset diff.
+ for (var i = 0; i < splices.length; i++) {
+ var current = splices[i];
+ current = splices[i] = new ListChangeRecord<E>(
+ current.object,
+ current.index + insertionOffset,
+ removed: current.removed,
+ addedCount: current.addedCount,
+ );
+
+ if (inserted) continue;
+
+ var intersectCount = _intersect(
+ spliceIndex,
+ spliceIndex + spliceRemoved.length,
+ current.index,
+ current.index + current.addedCount,
+ );
+ if (intersectCount >= 0) {
+ // Merge the two splices.
+ splices.removeAt(i);
+ i--;
+
+ insertionOffset -= current.addedCount - current.removed.length;
+ spliceAdded += current.addedCount - intersectCount;
+
+ final deleteCount =
+ spliceRemoved.length + current.removed.length - intersectCount;
+ if (spliceAdded == 0 && deleteCount == 0) {
+ // Merged splice is a no-op, discard.
+ inserted = true;
+ } else {
+ final removed = current.removed.toList();
+ if (spliceIndex < current.index) {
+ // Some prefix of splice.removed is prepended to current.removed.
+ removed.insertAll(
+ 0,
+ spliceRemoved.getRange(0, current.index - spliceIndex),
+ );
+ }
+ if (spliceIndex + spliceRemoved.length >
+ current.index + current.addedCount) {
+ // Some suffix of splice.removed is appended to current.removed.
+ removed.addAll(spliceRemoved.getRange(
+ current.index + current.addedCount - spliceIndex,
+ spliceRemoved.length,
+ ));
+ }
+ spliceRemoved = removed;
+ if (current.index < spliceIndex) {
+ spliceIndex = current.index;
+ }
+ }
+ } else if (spliceIndex < current.index) {
+ // Insert splice here.
+ inserted = true;
+ splices.insert(
+ i,
+ new ListChangeRecord<E>(
+ record.object,
+ spliceIndex,
+ removed: spliceRemoved,
+ addedCount: spliceAdded,
+ ),
+ );
+ i++;
+ final offset = spliceAdded - spliceRemoved.length;
+ current = splices[i] = new ListChangeRecord<E>(
+ current.object,
+ current.index + offset,
+ removed: current.removed,
+ addedCount: current.addedCount,
+ );
+ insertionOffset += offset;
+ }
+ }
+ if (!inserted) {
+ splices.add(new ListChangeRecord<E>(
+ record.object,
+ spliceIndex,
+ removed: spliceRemoved,
+ addedCount: spliceAdded,
+ ));
+ }
+}
+
+List<ListChangeRecord<E>> _createInitialSplices<E>(
+ List<E> list,
+ List<ListChangeRecord<E>> records,
+) {
+ final splices = <ListChangeRecord<E>>[];
+ for (var i = 0; i < records.length; i++) {
+ _mergeSplices(splices, records[i]);
+ }
+ return splices;
+}
+
+// We need to summarize change records. Consumers of these records want to
+// apply the batch sequentially, and ensure that they can find inserted
+// items by looking at that position in the list. This property does not
+// hold in our record-as-you-go records. Consider:
+//
+// var model = toObservable(['a', 'b']);
+// model.removeAt(1);
+// model.insertAll(0, ['c', 'd', 'e']);
+// model.removeRange(1, 3);
+// model.insert(1, 'f');
+//
+// Here, we inserted some records and then removed some of them.
+// If someone processed these records naively, they would "play back" the
+// insert incorrectly, because those items will be shifted.
+List<ListChangeRecord<E>> projectListSplices<E>(
+ List<E> list, List<ListChangeRecord<E>> records,
+ [Equality<E> equality]) {
+ equality ??= new DefaultEquality<E>();
+ if (records.length <= 1) return records;
+ final splices = <ListChangeRecord<E>>[];
+ final initialSplices = _createInitialSplices(list, records);
+ for (final splice in initialSplices) {
+ if (splice.addedCount == 1 && splice.removed.length == 1) {
+ if (splice.removed[0] != list[splice.index]) {
+ splices.add(splice);
+ }
+ continue;
+ }
+ splices.addAll(
+ _calcSplices(
+ list,
+ equality,
+ splice.index,
+ splice.index + splice.addedCount,
+ splice.removed,
+ 0,
+ splice.removed.length,
+ ),
+ );
+ }
+ return splices;
+}
diff --git a/packages/observable/lib/src/differs/map_differ.dart b/packages/observable/lib/src/differs/map_differ.dart
new file mode 100644
index 0000000..6f01fb7
--- /dev/null
+++ b/packages/observable/lib/src/differs/map_differ.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of observable.src.differs;
+
+/// Determines differences between two maps, returning [MapChangeRecord]s.
+///
+/// While [MapChangeRecord] has more information and can be replayed they carry
+/// a more significant cost to calculate and create and should only be used when
+/// the details in the record will actually be used.
+///
+/// See also [EqualityDiffer] for a simpler comparison.
+class MapDiffer<K, V> implements Differ<Map<K, V>> {
+ const MapDiffer();
+
+ @override
+ List<MapChangeRecord<K, V>> diff(Map<K, V> oldValue, Map<K, V> newValue) {
+ if (identical(oldValue, newValue)) {
+ return const [];
+ }
+ final changes = <MapChangeRecord<K, V>>[];
+ oldValue.forEach((oldK, oldV) {
+ final newV = newValue[oldK];
+ if (newV == null && !newValue.containsKey(oldK)) {
+ changes.add(new MapChangeRecord<K, V>.remove(oldK, oldV));
+ } else if (newV != oldV) {
+ changes.add(new MapChangeRecord<K, V>(oldK, oldV, newV));
+ }
+ });
+ newValue.forEach((newK, newV) {
+ if (!oldValue.containsKey(newK)) {
+ changes.add(new MapChangeRecord<K, V>.insert(newK, newV));
+ }
+ });
+ return freezeInDevMode(changes);
+ }
+}
diff --git a/packages/observable/lib/src/internal.dart b/packages/observable/lib/src/internal.dart
new file mode 100644
index 0000000..d600f50
--- /dev/null
+++ b/packages/observable/lib/src/internal.dart
@@ -0,0 +1,12 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+List<E> freezeInDevMode<E>(List<E> list) {
+ if (list == null) return const [];
+ assert(() {
+ list = new List<E>.unmodifiable(list);
+ return true;
+ }());
+ return list;
+}
diff --git a/packages/observable/lib/src/list_diff.dart b/packages/observable/lib/src/list_diff.dart
deleted file mode 100644
index c16a613..0000000
--- a/packages/observable/lib/src/list_diff.dart
+++ /dev/null
@@ -1,398 +0,0 @@
-// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-library observable.src.list_diff;
-
-import 'dart:collection' show UnmodifiableListView;
-import 'dart:math' as math;
-
-import 'change_record.dart' show ChangeRecord;
-
-/// A summary of an individual change to a [List].
-///
-/// Each delta represents that at the [index], [removed] sequence of items were
-/// removed, and counting forward from [index], [addedCount] items were added.
-class ListChangeRecord /* TODO(tvolkert): remove */ extends ChangeRecord {
- /// The list that changed.
- final List object;
-
- /// The index of the change.
- int get index => _index;
-
- /// The items removed, if any. Otherwise this will be an empty list.
- List get removed => _unmodifiableRemoved;
- UnmodifiableListView _unmodifiableRemoved;
-
- /// Mutable version of [removed], used during the algorithms as they are
- /// constructing the object.
- List _removed;
-
- /// The number of items added.
- int get addedCount => _addedCount;
-
- // Note: conceptually these are final, but for convenience we increment it as
- // we build the object. It will be "frozen" by the time it is returned the the
- // user.
- int _index, _addedCount;
-
- ListChangeRecord._(this.object, this._index, List removed, this._addedCount)
- : _removed = removed,
- _unmodifiableRemoved = new UnmodifiableListView(removed);
-
- factory ListChangeRecord(List object, int index,
- {List removed, int addedCount}) {
- if (removed == null) removed = [];
- if (addedCount == null) addedCount = 0;
- return new ListChangeRecord._(object, index, removed, addedCount);
- }
-
- /// Returns true if the provided [ref] index was changed by this operation.
- bool indexChanged(int ref) {
- // If ref is before the index, then it wasn't changed.
- if (ref < index) return false;
-
- // If this was a shift operation, anything after index is changed.
- if (addedCount != removed.length) return true;
-
- // Otherwise, anything in the update range was changed.
- return ref < index + addedCount;
- }
-
- String toString() => '#<ListChangeRecord index: $index, '
- 'removed: $removed, addedCount: $addedCount>';
-}
-
-// Note: This function is *based* on the computation of the Levenshtein
-// "edit" distance. The one change is that "updates" are treated as two
-// edits - not one. With List splices, an update is really a delete
-// followed by an add. By retaining this, we optimize for "keeping" the
-// maximum array items in the original array. For example:
-//
-// 'xxxx123' -> '123yyyy'
-//
-// With 1-edit updates, the shortest path would be just to update all seven
-// characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
-// leaves the substring '123' intact.
-List<List<int>> _calcEditDistances(List current, int currentStart,
- int currentEnd, List old, int oldStart, int oldEnd) {
- // "Deletion" columns
- int rowCount = oldEnd - oldStart + 1;
- int columnCount = currentEnd - currentStart + 1;
- List<List<int>> distances = new List<List<int>>(rowCount);
-
- // "Addition" rows. Initialize null column.
- for (int i = 0; i < rowCount; i++) {
- distances[i] = new List(columnCount);
- distances[i][0] = i;
- }
-
- // Initialize null row
- for (int j = 0; j < columnCount; j++) {
- distances[0][j] = j;
- }
-
- for (int i = 1; i < rowCount; i++) {
- for (int j = 1; j < columnCount; j++) {
- if (old[oldStart + i - 1] == current[currentStart + j - 1]) {
- distances[i][j] = distances[i - 1][j - 1];
- } else {
- int north = distances[i - 1][j] + 1;
- int west = distances[i][j - 1] + 1;
- distances[i][j] = math.min(north, west);
- }
- }
- }
-
- return distances;
-}
-
-const _kEditLeave = 0;
-const _kEditUpdate = 1;
-const _kEditAdd = 2;
-const _kEditDelete = 3;
-
-// This starts at the final weight, and walks "backward" by finding
-// the minimum previous weight recursively until the origin of the weight
-// matrix.
-List<int> _spliceOperationsFromEditDistances(List<List<int>> distances) {
- int i = distances.length - 1;
- int j = distances[0].length - 1;
- int current = distances[i][j];
- List<int> edits = <int>[];
- while (i > 0 || j > 0) {
- if (i == 0) {
- edits.add(_kEditAdd);
- j--;
- continue;
- }
- if (j == 0) {
- edits.add(_kEditDelete);
- i--;
- continue;
- }
- int northWest = distances[i - 1][j - 1];
- int west = distances[i - 1][j];
- int north = distances[i][j - 1];
-
- int min = math.min(math.min(west, north), northWest);
-
- if (min == northWest) {
- if (northWest == current) {
- edits.add(_kEditLeave);
- } else {
- edits.add(_kEditUpdate);
- current = northWest;
- }
- i--;
- j--;
- } else if (min == west) {
- edits.add(_kEditDelete);
- i--;
- current = west;
- } else {
- edits.add(_kEditAdd);
- j--;
- current = north;
- }
- }
-
- return edits.reversed.toList();
-}
-
-int _sharedPrefix(List arr1, List arr2, int searchLength) {
- for (int i = 0; i < searchLength; i++) {
- if (arr1[i] != arr2[i]) {
- return i;
- }
- }
- return searchLength;
-}
-
-int _sharedSuffix(List arr1, List arr2, int searchLength) {
- int index1 = arr1.length;
- int index2 = arr2.length;
- int count = 0;
- while (count < searchLength && arr1[--index1] == arr2[--index2]) {
- count++;
- }
- return count;
-}
-
-/// Lacking individual splice mutation information, the minimal set of
-/// splices can be synthesized given the previous state and final state of an
-/// array. The basic approach is to calculate the edit distance matrix and
-/// choose the shortest path through it.
-///
-/// Complexity: O(l * p)
-/// l: The length of the current array
-/// p: The length of the old array
-List<ListChangeRecord> calcSplices(List current, int currentStart,
- int currentEnd, List old, int oldStart, int oldEnd) {
- int prefixCount = 0;
- int suffixCount = 0;
-
- int minLength = math.min(currentEnd - currentStart, oldEnd - oldStart);
- if (currentStart == 0 && oldStart == 0) {
- prefixCount = _sharedPrefix(current, old, minLength);
- }
-
- if (currentEnd == current.length && oldEnd == old.length) {
- suffixCount = _sharedSuffix(current, old, minLength - prefixCount);
- }
-
- currentStart += prefixCount;
- oldStart += prefixCount;
- currentEnd -= suffixCount;
- oldEnd -= suffixCount;
-
- if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) {
- return const [];
- }
-
- if (currentStart == currentEnd) {
- ListChangeRecord splice = new ListChangeRecord(current, currentStart);
- while (oldStart < oldEnd) {
- splice._removed.add(old[oldStart++]);
- }
-
- return [splice];
- } else if (oldStart == oldEnd) {
- return [
- new ListChangeRecord(current, currentStart,
- addedCount: currentEnd - currentStart)
- ];
- }
-
- List<int> ops = _spliceOperationsFromEditDistances(_calcEditDistances(
- current, currentStart, currentEnd, old, oldStart, oldEnd));
-
- ListChangeRecord splice;
- List<ListChangeRecord> splices = <ListChangeRecord>[];
- int index = currentStart;
- int oldIndex = oldStart;
- for (int i = 0; i < ops.length; i++) {
- switch (ops[i]) {
- case _kEditLeave:
- if (splice != null) {
- splices.add(splice);
- splice = null;
- }
-
- index++;
- oldIndex++;
- break;
- case _kEditUpdate:
- if (splice == null) splice = new ListChangeRecord(current, index);
-
- splice._addedCount++;
- index++;
-
- splice._removed.add(old[oldIndex]);
- oldIndex++;
- break;
- case _kEditAdd:
- if (splice == null) splice = new ListChangeRecord(current, index);
-
- splice._addedCount++;
- index++;
- break;
- case _kEditDelete:
- if (splice == null) splice = new ListChangeRecord(current, index);
-
- splice._removed.add(old[oldIndex]);
- oldIndex++;
- break;
- }
- }
-
- if (splice != null) splices.add(splice);
- return splices;
-}
-
-int _intersect(int start1, int end1, int start2, int end2) =>
- math.min(end1, end2) - math.max(start1, start2);
-
-void _mergeSplice(List<ListChangeRecord> splices, ListChangeRecord record) {
- ListChangeRecord splice = new ListChangeRecord(record.object, record.index,
- removed: record._removed.toList(), addedCount: record.addedCount);
-
- bool inserted = false;
- int insertionOffset = 0;
-
- // I think the way this works is:
- // - the loop finds where the merge should happen
- // - it applies the merge in a particular splice
- // - then continues and updates the subsequent splices with any offset diff.
- for (int i = 0; i < splices.length; i++) {
- final ListChangeRecord current = splices[i];
- current._index += insertionOffset;
-
- if (inserted) continue;
-
- int intersectCount = _intersect(
- splice.index,
- splice.index + splice.removed.length,
- current.index,
- current.index + current.addedCount);
-
- if (intersectCount >= 0) {
- // Merge the two splices
-
- splices.removeAt(i);
- i--;
-
- insertionOffset -= current.addedCount - current.removed.length;
-
- splice._addedCount += current.addedCount - intersectCount;
- int deleteCount =
- splice.removed.length + current.removed.length - intersectCount;
-
- if (splice.addedCount == 0 && deleteCount == 0) {
- // merged splice is a noop. discard.
- inserted = true;
- } else {
- List removed = current._removed;
-
- if (splice.index < current.index) {
- // some prefix of splice.removed is prepended to current.removed.
- removed.insertAll(
- 0, splice.removed.getRange(0, current.index - splice.index));
- }
-
- if (splice.index + splice.removed.length >
- current.index + current.addedCount) {
- // some suffix of splice.removed is appended to current.removed.
- removed.addAll(splice.removed.getRange(
- current.index + current.addedCount - splice.index,
- splice.removed.length));
- }
-
- splice._removed = removed;
- splice._unmodifiableRemoved = current._unmodifiableRemoved;
- if (current.index < splice.index) {
- splice._index = current.index;
- }
- }
- } else if (splice.index < current.index) {
- // Insert splice here.
-
- inserted = true;
-
- splices.insert(i, splice);
- i++;
-
- int offset = splice.addedCount - splice.removed.length;
- current._index += offset;
- insertionOffset += offset;
- }
- }
-
- if (!inserted) splices.add(splice);
-}
-
-List<ListChangeRecord> _createInitialSplices(
- List<Object> list, List<ListChangeRecord> records) {
- List<ListChangeRecord> splices = [];
- for (ListChangeRecord record in records) {
- _mergeSplice(splices, record);
- }
- return splices;
-}
-
-/// We need to summarize change records. Consumers of these records want to
-/// apply the batch sequentially, and ensure that they can find inserted
-/// items by looking at that position in the list. This property does not
-/// hold in our record-as-you-go records. Consider:
-///
-/// var model = toObservable(['a', 'b']);
-/// model.removeAt(1);
-/// model.insertAll(0, ['c', 'd', 'e']);
-/// model.removeRange(1, 3);
-/// model.insert(1, 'f');
-///
-/// Here, we inserted some records and then removed some of them.
-/// If someone processed these records naively, they would "play back" the
-/// insert incorrectly, because those items will be shifted.
-List<ListChangeRecord> projectListSplices(
- List<Object> list, List<ListChangeRecord> records) {
- if (records.length <= 1) return records;
-
- List<ListChangeRecord> splices = <ListChangeRecord>[];
- for (ListChangeRecord splice in _createInitialSplices(list, records)) {
- if (splice.addedCount == 1 && splice.removed.length == 1) {
- if (splice.removed[0] != list[splice.index]) splices.add(splice);
- continue;
- }
-
- splices.addAll(calcSplices(
- list,
- splice.index,
- splice.index + splice.addedCount,
- splice._removed,
- 0,
- splice.removed.length));
- }
-
- return splices;
-}
diff --git a/packages/observable/lib/src/observable.dart b/packages/observable/lib/src/observable.dart
index 7d1ec6f..edc536b 100644
--- a/packages/observable/lib/src/observable.dart
+++ b/packages/observable/lib/src/observable.dart
@@ -5,74 +5,57 @@
library observable.src.observable;
import 'dart:async';
-import 'dart:collection' show UnmodifiableListView;
import 'package:meta/meta.dart';
-import 'change_record.dart' show ChangeRecord;
-import 'property_change_record.dart' show PropertyChangeRecord;
+import 'change_notifier.dart';
+import 'records.dart';
-/// Represents an object with observable properties. This is used by data in
-/// model-view architectures to notify interested parties of [changes] to the
-/// object's properties (fields or getter/setter pairs).
+/// Represents an object with observable state or properties.
///
/// The interface does not require any specific technique to implement
-/// observability. You can implement it in the following ways:
-///
-/// - Deriving from this class via a mixin or base class. When a field,
-/// property, or indexable item is changed, the derived class should call
-/// [notifyPropertyChange]. See that method for an example.
-/// - Implementing this interface and providing your own implementation.
-abstract class Observable {
- StreamController<List<ChangeRecord>> _changes;
+/// observability. You may implement it in the following ways:
+/// - Extend or mixin [ChangeNotifier]
+/// - Implement the interface yourself and provide your own implementation
+abstract class Observable<C extends ChangeRecord> {
+ // To be removed when https://github.com/dart-lang/observable/issues/10
+ final ChangeNotifier<C> _delegate = new ChangeNotifier<C>();
- List<ChangeRecord> _records;
+ // Whether Observable was not given a type.
+ final bool _isNotGeneric = C == dynamic || C == ChangeRecord;
- /// The stream of property change records to this object, delivered
- /// asynchronously.
+ /// Emits a list of changes when the state of the object changes.
///
- /// [deliverChanges] can be called to force synchronous delivery.
- Stream<List<ChangeRecord>> get changes {
- if (_changes == null) {
- _changes = new StreamController.broadcast(
- sync: true, onListen: observed, onCancel: unobserved);
- }
- return _changes.stream;
- }
+ /// Changes should produced in order, if significant.
+ Stream<List<C>> get changes => _delegate.changes;
- /// Derived classes may override this method to be called when the [changes]
- /// are first observed.
- // TODO(tvolkert): @mustCallSuper (github.com/dart-lang/sdk/issues/27275)
+ /// May override to be notified when [changes] is first observed.
@protected
- void observed() {}
+ @mustCallSuper
+ @Deprecated('Use ChangeNotifier instead to have this method available')
+ // REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10
+ // ignore: invalid_use_of_protected_member
+ void observed() => _delegate.observed();
- /// Derived classes may override this method to be called when the [changes]
- /// are no longer being observed.
- // TODO(tvolkert): @mustCallSuper (github.com/dart-lang/sdk/issues/27275)
+ /// May override to be notified when [changes] is no longer observed.
@protected
- void unobserved() {
- // Free some memory
- _changes = null;
- }
+ @mustCallSuper
+ @Deprecated('Use ChangeNotifier instead to have this method available')
+ // REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10
+ // ignore: invalid_use_of_protected_member
+ void unobserved() => _delegate.unobserved();
/// True if this object has any observers.
- bool get hasObservers => _changes != null && _changes.hasListener;
+ @Deprecated('Use ChangeNotifier instead to have this method available')
+ bool get hasObservers => _delegate.hasObservers;
- /// Synchronously deliver pending [changes].
+ /// If [hasObservers], synchronously emits [changes] that have been queued.
///
- /// Returns `true` if any records were delivered, otherwise `false`.
- /// Pending records will be cleared regardless, to keep newly added
- /// observers from being notified of changes that occurred before
- /// they started observing.
- bool deliverChanges() {
- List<ChangeRecord> records = _records;
- _records = null;
- if (hasObservers && records != null) {
- _changes.add(new UnmodifiableListView<ChangeRecord>(records));
- return true;
- }
- return false;
- }
+ /// Returns `true` if changes were emitted.
+ @Deprecated('Use ChangeNotifier instead to have this method available')
+ // REMOVE IGNORE when https://github.com/dart-lang/observable/issues/10
+ // ignore: invalid_use_of_protected_member
+ bool deliverChanges() => _delegate.deliverChanges();
/// Notify that the [field] name of this object has been changed.
///
@@ -80,27 +63,45 @@
/// equal, no change will be recorded.
///
/// For convenience this returns [newValue].
- /*=T*/ notifyPropertyChange/*<T>*/(
- Symbol field, /*=T*/ oldValue, /*=T*/ newValue) {
+ ///
+ /// ## Deprecated
+ ///
+ /// All [Observable] objects will no longer be required to emit change records
+ /// when any property changes. For example, `ObservableList` will only emit
+ /// on `ObservableList.changes`, instead of on `ObservableList.listChanges`.
+ ///
+ /// If you are using a typed `implements/extends Observable<C>`, it is illegal
+ /// to call this method - will throw an [UnsupportedError] when called.
+ @Deprecated('Use PropertyChangeNotifier')
+ T notifyPropertyChange<T>(
+ Symbol field,
+ T oldValue,
+ T newValue,
+ ) {
if (hasObservers && oldValue != newValue) {
- notifyChange(new PropertyChangeRecord(this, field, oldValue, newValue));
+ if (_isNotGeneric) {
+ notifyChange(
+ new PropertyChangeRecord(
+ this,
+ field,
+ oldValue,
+ newValue,
+ ) as C,
+ );
+ } else {
+ // Internal specific patch: Just do nothing.
+ //
+ // Generic typed Observable does not support.
+ }
}
return newValue;
}
- /// Notify observers of a change.
+ /// Schedules [change] to be delivered.
///
- /// This will automatically schedule [deliverChanges].
+ /// If [change] is omitted then [ChangeRecord.ANY] will be sent.
///
- /// For most objects [Observable.notifyPropertyChange] is more convenient, but
- /// collections sometimes deliver other types of changes such as a
- /// [MapChangeRecord].
- void notifyChange(ChangeRecord record) {
- if (!hasObservers) return;
- if (_records == null) {
- _records = [];
- scheduleMicrotask(deliverChanges);
- }
- _records.add(record);
- }
+ /// If there are no listeners to [changes], this method does nothing.
+ @Deprecated('Use ChangeNotifier instead to have this method available')
+ void notifyChange([C change]) => _delegate.notifyChange(change);
}
diff --git a/packages/observable/lib/src/observable_list.dart b/packages/observable/lib/src/observable_list.dart
index 16c49f5..d91bb68 100644
--- a/packages/observable/lib/src/observable_list.dart
+++ b/packages/observable/lib/src/observable_list.dart
@@ -7,16 +7,31 @@
import 'dart:async';
import 'dart:collection' show ListBase, UnmodifiableListView;
-import 'list_diff.dart' show ListChangeRecord, projectListSplices, calcSplices;
+import 'differs.dart';
+import 'records.dart';
import 'observable.dart' show Observable;
/// Represents an observable list of model values. If any items are added,
/// removed, or replaced, then observers that are listening to [changes]
/// will be notified.
class ObservableList<E> extends ListBase<E> with Observable {
- List<ListChangeRecord> _listRecords;
+ /// Adapts [source] to be a `ObservableList<T>`.
+ ///
+ /// Any time the list would produce an element that is not a [T],
+ /// the element access will throw.
+ ///
+ /// Any time a [T] value is attempted stored into the adapted list,
+ /// the store will throw unless the value is also an instance of [S].
+ ///
+ /// If all accessed elements of [source] are actually instances of [T],
+ /// and if all elements stored into the returned list are actually instance
+ /// of [S], then the returned list can be used as a `ObservableList<T>`.
+ static ObservableList<T> castFrom<S, T>(ObservableList<S> source) =>
+ new ObservableList<T>._spy(source._list.cast<T>());
- StreamController<List<ListChangeRecord>> _listChanges;
+ List<ListChangeRecord<E>> _listRecords;
+
+ StreamController<List<ListChangeRecord<E>>> _listChanges;
/// The inner [List<E>] with the actual storage.
final List<E> _list;
@@ -41,8 +56,37 @@
/// Creates an observable list with the elements of [other]. The order in
/// the list will be the order provided by the iterator of [other].
- factory ObservableList.from(Iterable<E> other) =>
- new ObservableList<E>()..addAll(other);
+ ObservableList.from(Iterable other) : _list = new List<E>.from(other);
+
+ ObservableList._spy(List<E> other) : _list = other;
+
+ /// Returns a view of this list as a list of [T] instances.
+ ///
+ /// If this list contains only instances of [T], all read operations
+ /// will work correctly. If any operation tries to access an element
+ /// that is not an instance of [T], the access will throw instead.
+ ///
+ /// Elements added to the list (e.g., by using [add] or [addAll])
+ /// must be instance of [T] to be valid arguments to the adding function,
+ /// and they must be instances of [E] as well to be accepted by
+ /// this list as well.
+ @override
+ ObservableList<T> cast<T>() => ObservableList.castFrom<E, T>(this);
+
+ /// Returns a view of this list as a list of [T] instances.
+ ///
+ /// If this list contains only instances of [T], all read operations
+ /// will work correctly. If any operation tries to access an element
+ /// that is not an instance of [T], the access will throw instead.
+ ///
+ /// Elements added to the list (e.g., by using [add] or [addAll])
+ /// must be instance of [T] to be valid arguments to the adding function,
+ /// and they must be instances of [E] as well to be accepted by
+ /// this list as well.
+ @deprecated
+ @override
+ // ignore: override_on_non_overriding_method
+ ObservableList<T> retype<T>() => cast<T>();
/// The stream of summarized list changes, delivered asynchronously.
///
@@ -64,7 +108,7 @@
/// #<ListChangeRecord index: 3, removed: [b], addedCount: 0>
///
/// [deliverChanges] can be called to force synchronous delivery.
- Stream<List<ListChangeRecord>> get listChanges {
+ Stream<List<ListChangeRecord<E>>> get listChanges {
if (_listChanges == null) {
// TODO(jmesserly): split observed/unobserved notions?
_listChanges = new StreamController.broadcast(
@@ -79,8 +123,10 @@
bool get hasListObservers => _listChanges != null && _listChanges.hasListener;
+ @override
int get length => _list.length;
+ @override
set length(int value) {
int len = _list.length;
if (len == value) return;
@@ -98,8 +144,10 @@
_list.length = value;
}
+ @override
E operator [](int index) => _list[index];
+ @override
void operator []=(int index, E value) {
E oldValue = _list[index];
if (hasListObservers && oldValue != value) {
@@ -109,7 +157,10 @@
}
// Forwarders so we can reflect on the properties.
+ @override
bool get isEmpty => super.isEmpty;
+
+ @override
bool get isNotEmpty => super.isNotEmpty;
// TODO(jmesserly): should we support first/last/single? They're kind of
@@ -118,7 +169,7 @@
// existing list notifications.
// The following methods are here so that we can provide nice change events.
-
+ @override
void setAll(int index, Iterable<E> iterable) {
if (iterable is! List && iterable is! Set) {
iterable = iterable.toList();
@@ -131,6 +182,7 @@
_list.setAll(index, iterable);
}
+ @override
void add(E value) {
int len = _list.length;
_notifyChangeLength(len, len + 1);
@@ -141,6 +193,7 @@
_list.add(value);
}
+ @override
void addAll(Iterable<E> iterable) {
int len = _list.length;
_list.addAll(iterable);
@@ -153,6 +206,7 @@
}
}
+ @override
bool remove(Object element) {
for (int i = 0; i < this.length; i++) {
if (this[i] == element) {
@@ -163,6 +217,7 @@
return false;
}
+ @override
void removeRange(int start, int end) {
_rangeCheck(start, end);
int rangeLength = end - start;
@@ -176,6 +231,7 @@
_list.removeRange(start, end);
}
+ @override
void insertAll(int index, Iterable<E> iterable) {
if (index < 0 || index > length) {
throw new RangeError.range(index, 0, length);
@@ -201,6 +257,7 @@
}
}
+ @override
void insert(int index, E element) {
if (index < 0 || index > length) {
throw new RangeError.range(index, 0, length);
@@ -223,6 +280,7 @@
_list[index] = element;
}
+ @override
E removeAt(int index) {
E result = this[index];
removeRange(index, index + 1);
@@ -238,14 +296,22 @@
}
}
- void _notifyListChange(int index, {List removed, int addedCount}) {
+ void _notifyListChange(
+ int index, {
+ List<E> removed,
+ int addedCount: 0,
+ }) {
if (!hasListObservers) return;
if (_listRecords == null) {
_listRecords = [];
scheduleMicrotask(deliverListChanges);
}
- _listRecords.add(new ListChangeRecord(this, index,
- removed: removed, addedCount: addedCount));
+ _listRecords.add(new ListChangeRecord<E>(
+ this,
+ index,
+ removed: removed,
+ addedCount: addedCount,
+ ));
}
void _notifyChangeLength(int oldValue, int newValue) {
@@ -261,11 +327,11 @@
bool deliverListChanges() {
if (_listRecords == null) return false;
- List<ListChangeRecord> records = projectListSplices(this, _listRecords);
+ final records = projectListSplices<E>(this, _listRecords);
_listRecords = null;
if (hasListObservers && records.isNotEmpty) {
- _listChanges.add(new UnmodifiableListView<ListChangeRecord>(records));
+ _listChanges.add(new UnmodifiableListView<ListChangeRecord<E>>(records));
return true;
}
return false;
@@ -283,9 +349,12 @@
///
/// Complexity is `O(l * p)` where `l` is the length of the current list and
/// `p` is the length of the old list.
- static List<ListChangeRecord> calculateChangeRecords(
- List<Object> oldValue, List<Object> newValue) =>
- calcSplices(newValue, 0, newValue.length, oldValue, 0, oldValue.length);
+ static List<ListChangeRecord<E>> calculateChangeRecords<E>(
+ List<E> oldValue,
+ List<E> newValue,
+ ) {
+ return new ListDiffer<E>().diff(oldValue, newValue);
+ }
/// Updates the [previous] list using the [changeRecords]. For added items,
/// the [current] list is used to find the current value.
diff --git a/packages/observable/lib/src/observable_map.dart b/packages/observable/lib/src/observable_map.dart
index 23a7f11..bb1c844 100644
--- a/packages/observable/lib/src/observable_map.dart
+++ b/packages/observable/lib/src/observable_map.dart
@@ -6,9 +6,9 @@
import 'dart:collection';
-import 'change_record.dart' show ChangeRecord;
-import 'observable.dart' show Observable;
-import 'property_change_record.dart' show PropertyChangeRecord;
+import 'observable.dart';
+import 'records.dart';
+import 'to_observable.dart';
// TODO(jmesserly): this needs to be faster. We currently require multiple
// lookups per key to get the old value.
@@ -16,50 +16,28 @@
// LinkedHashMap, SplayTreeMap or HashMap. However it can use them for the
// backing store.
-// TODO(jmesserly): should we summarize map changes like we do for list changes?
-class MapChangeRecord<K, V> extends ChangeRecord {
- // TODO(jmesserly): we could store this more compactly if it matters, with
- // subtypes for inserted and removed.
-
- /// The map key that changed.
- final K key;
-
- /// The previous value associated with this key.
- final V oldValue;
-
- /// The new value associated with this key.
- final V newValue;
-
- /// True if this key was inserted.
- final bool isInsert;
-
- /// True if this key was removed.
- final bool isRemove;
-
- MapChangeRecord(this.key, this.oldValue, this.newValue)
- : isInsert = false,
- isRemove = false;
-
- MapChangeRecord.insert(this.key, this.newValue)
- : isInsert = true,
- isRemove = false,
- oldValue = null;
-
- MapChangeRecord.remove(this.key, this.oldValue)
- : isInsert = false,
- isRemove = true,
- newValue = null;
-
- String toString() {
- var kind = isInsert ? 'insert' : isRemove ? 'remove' : 'set';
- return '#<MapChangeRecord $kind $key from: $oldValue to: $newValue>';
- }
-}
-
/// Represents an observable map of model values. If any items are added,
/// removed, or replaced, then observers that are listening to [changes]
/// will be notified.
class ObservableMap<K, V> extends Observable implements Map<K, V> {
+ /// Adapts [source] to be a `ObservableMap<K2, V2>`.
+ ///
+ /// Any time the map would produce a key or value that is not a [K2] or [V2]
+ /// the access will throw.
+ ///
+ /// Any time [K2] key or [V2] value is attempted added into the adapted map,
+ /// the store will throw unless the key is also an instance of [K] and the
+ /// value is also an instance of [V].
+ ///
+ /// If all accessed entries of [source] have [K2] keys and [V2] values and if
+ /// all entries added to the returned map have [K] keys and [V] values, then
+ /// the returned map can be used as a `Map<K2, V2>`.
+ static ObservableMap<K2, V2> castFrom<K, V, K2, V2>(
+ ObservableMap<K, V> source,
+ ) {
+ return new ObservableMap<K2, V2>.spy(source._map.cast<K2, V2>());
+ }
+
final Map<K, V> _map;
/// Creates an observable map.
@@ -95,22 +73,34 @@
return result;
}
+ /// Creates a new observable map wrapping [other].
+ ObservableMap.spy(Map<K, V> other) : _map = other;
+
+ @override
Iterable<K> get keys => _map.keys;
+ @override
Iterable<V> get values => _map.values;
+ @override
int get length => _map.length;
+ @override
bool get isEmpty => length == 0;
+ @override
bool get isNotEmpty => !isEmpty;
+ @override
bool containsValue(Object value) => _map.containsValue(value);
+ @override
bool containsKey(Object key) => _map.containsKey(key);
+ @override
V operator [](Object key) => _map[key];
+ @override
void operator []=(K key, V value) {
if (!hasObservers) {
_map[key] = value;
@@ -132,12 +122,14 @@
}
}
+ @override
void addAll(Map<K, V> other) {
other.forEach((K key, V value) {
this[key] = value;
});
}
+ @override
V putIfAbsent(K key, V ifAbsent()) {
int len = _map.length;
V result = _map.putIfAbsent(key, ifAbsent);
@@ -149,6 +141,7 @@
return result;
}
+ @override
V remove(Object key) {
int len = _map.length;
V result = _map.remove(key);
@@ -160,6 +153,7 @@
return result;
}
+ @override
void clear() {
int len = _map.length;
if (hasObservers && len > 0) {
@@ -172,9 +166,47 @@
_map.clear();
}
+ @override
void forEach(void f(K key, V value)) => _map.forEach(f);
- String toString() => Maps.mapToString(this);
+ @override
+ String toString() => MapBase.mapToString(this);
+
+ @override
+ ObservableMap<K2, V2> cast<K2, V2>() {
+ return ObservableMap.castFrom<K, V, K2, V2>(this);
+ }
+
+ @deprecated
+ @override
+ // ignore: override_on_non_overriding_method
+ ObservableMap<K2, V2> retype<K2, V2>() {
+ return ObservableMap.castFrom<K, V, K2, V2>(this);
+ }
+
+ @override
+ Iterable<MapEntry<K, V>> get entries => _map.entries;
+
+ @override
+ void addEntries(Iterable<MapEntry<K, V>> entries) {
+ _map.addEntries(entries);
+ }
+
+ @override
+ Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> transform(K key, V value)) {
+ return _map.map(transform);
+ }
+
+ @override
+ V update(K key, V update(V value), {V ifAbsent()}) {
+ return _map.update(key, update, ifAbsent: ifAbsent);
+ }
+
+ @override
+ void updateAll(V update(K key, V value)) => _map.updateAll(update);
+
+ @override
+ void removeWhere(bool test(K key, V value)) => _map.removeWhere(test);
// Note: we don't really have a reasonable old/new value to use here.
// But this should fix "keys" and "values" in templates with minimal overhead.
diff --git a/packages/observable/lib/src/property_change_record.dart b/packages/observable/lib/src/property_change_record.dart
deleted file mode 100644
index a09c196..0000000
--- a/packages/observable/lib/src/property_change_record.dart
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-library observable.src.property_change_record;
-
-import 'change_record.dart';
-
-/// A change record to a field of an [Observable] object.
-class PropertyChangeRecord<T> extends ChangeRecord {
- /// The object that changed.
- final Object object;
-
- /// The name of the property that changed.
- final Symbol name;
-
- /// The previous value of the property.
- final T oldValue;
-
- /// The new value of the property.
- final T newValue;
-
- PropertyChangeRecord(this.object, this.name, this.oldValue, this.newValue);
-
- String toString() =>
- '#<PropertyChangeRecord $name from: $oldValue to: $newValue>';
-}
diff --git a/packages/observable/lib/src/records.dart b/packages/observable/lib/src/records.dart
new file mode 100644
index 0000000..87d7d4f
--- /dev/null
+++ b/packages/observable/lib/src/records.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library observable.src.records;
+
+import 'package:collection/collection.dart';
+import 'package:quiver/core.dart';
+
+import 'internal.dart';
+
+part 'records/list_change_record.dart';
+part 'records/map_change_record.dart';
+part 'records/property_change_record.dart';
+
+/// Result of a change to an observed object.
+class ChangeRecord {
+ /// Signifies a change occurred, but without details of the specific change.
+ ///
+ /// May be used to produce lower-GC-pressure records where more verbose change
+ /// records will not be used directly.
+ static const List<ChangeRecord> ANY = const [const ChangeRecord()];
+
+ /// Signifies no changes occurred.
+ static const List<ChangeRecord> NONE = const [];
+
+ const ChangeRecord();
+}
diff --git a/packages/observable/lib/src/records/list_change_record.dart b/packages/observable/lib/src/records/list_change_record.dart
new file mode 100644
index 0000000..321a949
--- /dev/null
+++ b/packages/observable/lib/src/records/list_change_record.dart
@@ -0,0 +1,134 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of observable.src.records;
+
+/// A [ChangeRecord] that denotes adding or removing nodes at [index].
+///
+/// It should be assumed that elements are [removed] *before* being added.
+///
+/// A [List<ListChangeRecord>] can be "played back" against the [List] using
+/// the final list positions to figure out which item was added - this removes
+/// the need to incur costly GC on the most common operation (adding).
+class ListChangeRecord<E> implements ChangeRecord {
+ /// How many elements were added at [index] (after removing elements).
+ final int addedCount;
+
+ /// Index of where the change occurred.
+ final int index;
+
+ /// List that changed.
+ final List<E> object;
+
+ /// Elements that were removed starting at [index] (before adding elements).
+ final List<E> removed;
+
+ factory ListChangeRecord(
+ List<E> object,
+ int index, {
+ List<E> removed,
+ int addedCount: 0,
+ }) {
+ return new ListChangeRecord._(
+ object, index, removed ?? new UnmodifiableListView([]), addedCount);
+ }
+
+ /// Records an `add` operation at `object[index]` of [addedCount] elements.
+ ListChangeRecord.add(this.object, this.index, this.addedCount)
+ : removed = new UnmodifiableListView([]) {
+ _assertValidState();
+ }
+
+ /// Records a `remove` operation at `object[index]` of [removed] elements.
+ ListChangeRecord.remove(this.object, this.index, List<E> removed)
+ : this.removed = freezeInDevMode<E>(removed),
+ this.addedCount = 0 {
+ _assertValidState();
+ }
+
+ /// Records a `replace` operation at `object[index]` of [removed] elements.
+ ///
+ /// If [addedCount] is not specified it defaults to `removed.length`.
+ ListChangeRecord.replace(this.object, this.index, List<E> removed,
+ [int addedCount])
+ : this.removed = freezeInDevMode<E>(removed),
+ this.addedCount = addedCount ?? removed.length {
+ _assertValidState();
+ }
+
+ ListChangeRecord._(
+ this.object,
+ this.index,
+ this.removed,
+ this.addedCount,
+ ) {
+ _assertValidState();
+ }
+
+ /// What elements were added to [object].
+ Iterable<E> get added {
+ return addedCount == 0
+ ? const []
+ : object.getRange(index, index + addedCount);
+ }
+
+ /// Apply this change record to [list].
+ void apply(List<E> list) {
+ list
+ ..removeRange(index, index + removed.length)
+ ..insertAll(index, object.getRange(index, index + addedCount));
+ }
+
+ void _assertValidState() {
+ assert(() {
+ if (object == null) {
+ throw new ArgumentError.notNull('object');
+ }
+ if (index == null) {
+ throw new ArgumentError.notNull('index');
+ }
+ if (removed == null) {
+ throw new ArgumentError.notNull('removed');
+ }
+ if (addedCount == null || addedCount < 0) {
+ throw new ArgumentError('Invalid `addedCount`: $addedCount');
+ }
+ return true;
+ }());
+ }
+
+ /// Returns whether [reference] index was changed in this operation.
+ bool indexChanged(int reference) {
+ // If reference was before the change then it wasn't changed.
+ if (reference < index) return false;
+
+ // If this was a shift operation anything after index is changed.
+ if (addedCount != removed.length) return true;
+
+ // Otherwise anything in the update range was changed.
+ return reference < index + addedCount;
+ }
+
+ @override
+ bool operator ==(Object o) {
+ if (o is ListChangeRecord<E>) {
+ return identical(object, o.object) &&
+ index == o.index &&
+ addedCount == o.addedCount &&
+ const ListEquality().equals(removed, o.removed);
+ }
+ return false;
+ }
+
+ @override
+ int get hashCode {
+ return hash4(object, index, addedCount, const ListEquality().hash(removed));
+ }
+
+ @override
+ String toString() => ''
+ '#<$ListChangeRecord index: $index, '
+ 'removed: $removed, '
+ 'addedCount: $addedCount>';
+}
diff --git a/packages/observable/lib/src/records/map_change_record.dart b/packages/observable/lib/src/records/map_change_record.dart
new file mode 100644
index 0000000..cd37fc9
--- /dev/null
+++ b/packages/observable/lib/src/records/map_change_record.dart
@@ -0,0 +1,82 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of observable.src.records;
+
+/// A [ChangeRecord] that denotes adding, removing, or updating a map.
+class MapChangeRecord<K, V> implements ChangeRecord {
+ /// The map key that changed.
+ final K key;
+
+ /// The previous value associated with this key.
+ ///
+ /// Is always `null` if [isInsert].
+ final V oldValue;
+
+ /// The new value associated with this key.
+ ///
+ /// Is always `null` if [isRemove].
+ final V newValue;
+
+ /// True if this key was inserted.
+ final bool isInsert;
+
+ /// True if this key was removed.
+ final bool isRemove;
+
+ /// Create an update record of [key] from [oldValue] to [newValue].
+ const MapChangeRecord(this.key, this.oldValue, this.newValue)
+ : isInsert = false,
+ isRemove = false;
+
+ /// Create an insert record of [key] and [newValue].
+ const MapChangeRecord.insert(this.key, this.newValue)
+ : isInsert = true,
+ isRemove = false,
+ oldValue = null;
+
+ /// Create a remove record of [key] with a former [oldValue].
+ const MapChangeRecord.remove(this.key, this.oldValue)
+ : isInsert = false,
+ isRemove = true,
+ newValue = null;
+
+ /// Apply this change record to [map].
+ void apply(Map<K, V> map) {
+ if (isRemove) {
+ map.remove(key);
+ } else {
+ map[key] = newValue;
+ }
+ }
+
+ @override
+ bool operator ==(Object o) {
+ if (o is MapChangeRecord<K, V>) {
+ return key == o.key &&
+ oldValue == o.oldValue &&
+ newValue == o.newValue &&
+ isInsert == o.isInsert &&
+ isRemove == o.isRemove;
+ }
+ return false;
+ }
+
+ @override
+ int get hashCode {
+ return hashObjects([
+ key,
+ oldValue,
+ newValue,
+ isInsert,
+ isRemove,
+ ]);
+ }
+
+ @override
+ String toString() {
+ final kind = isInsert ? 'insert' : isRemove ? 'remove' : 'set';
+ return '#<MapChangeRecord $kind $key from $oldValue to $newValue';
+ }
+}
diff --git a/packages/observable/lib/src/records/property_change_record.dart b/packages/observable/lib/src/records/property_change_record.dart
new file mode 100644
index 0000000..74566d5
--- /dev/null
+++ b/packages/observable/lib/src/records/property_change_record.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of observable.src.records;
+
+/// A change record to a field of a generic observable object.
+class PropertyChangeRecord<T> implements ChangeRecord {
+ /// Object that changed.
+ final Object object;
+
+ /// Name of the property that changed.
+ final Symbol name;
+
+ /// Previous value of the property.
+ final T oldValue;
+
+ /// New value of the property.
+ final T newValue;
+
+ const PropertyChangeRecord(
+ this.object,
+ this.name,
+ this.oldValue,
+ this.newValue,
+ );
+
+ @override
+ String toString() => ''
+ '#<$PropertyChangeRecord $name from $oldValue to: $newValue';
+}
diff --git a/packages/observable/lib/src/to_observable.dart b/packages/observable/lib/src/to_observable.dart
index 0a0f316..c1ddc96 100644
--- a/packages/observable/lib/src/to_observable.dart
+++ b/packages/observable/lib/src/to_observable.dart
@@ -4,6 +4,10 @@
library observable.src.to_observable;
+import 'dart:collection';
+
+import 'package:dart_internal/extract_type_arguments.dart';
+
import 'observable.dart' show Observable;
import 'observable_list.dart' show ObservableList;
import 'observable_map.dart' show ObservableMap;
@@ -28,22 +32,42 @@
dynamic _toObservableShallow(dynamic value) {
if (value is Observable) return value;
- if (value is Map) return new ObservableMap.from(value);
- if (value is Iterable) return new ObservableList.from(value);
+
+ if (value is Map) {
+ return extractMapTypeArguments(
+ value, <K, V>() => new ObservableMap<K, V>.from(value));
+ }
+
+ if (value is Iterable) {
+ return extractIterableTypeArgument(
+ value, <T>() => new ObservableList<T>.from(value));
+ }
+
return value;
}
dynamic _toObservableDeep(dynamic value) {
if (value is Observable) return value;
+
if (value is Map) {
- var result = new ObservableMap.createFromType(value);
- value.forEach((k, v) {
- result[_toObservableDeep(k)] = _toObservableDeep(v);
+ return extractMapTypeArguments(value, <K, V>() {
+ var result = new ObservableMap<K, V>.createFromType(value);
+ value.forEach((k, v) {
+ result[_toObservableDeep(k)] = _toObservableDeep(v);
+ });
+ return result;
});
- return result;
}
+
if (value is Iterable) {
- return new ObservableList.from(value.map(_toObservableDeep));
+ return extractIterableTypeArgument(value, <T>() {
+ var result = new ObservableList<T>();
+ for (var element in value) {
+ result.add(_toObservableDeep(element));
+ }
+ return result;
+ });
}
+
return value;
}
diff --git a/packages/observable/pubspec.yaml b/packages/observable/pubspec.yaml
index b030500..593b4a7 100644
--- a/packages/observable/pubspec.yaml
+++ b/packages/observable/pubspec.yaml
@@ -1,11 +1,18 @@
name: observable
-version: 0.14.0+1
+version: 0.22.1+3
author: Dart Team <misc@dartlang.org>
description: Support for marking objects as observable
homepage: https://github.com/dart-lang/observable
environment:
- sdk: '>=1.19.0 <2.0.0'
+ sdk: '>=2.0.0-dev.55.0 <2.0.0'
dependencies:
+ collection: '^1.11.0'
+ dart_internal: '^0.1.1'
meta: '^1.0.4'
+ quiver: '>=0.24.0 <0.30.0'
dev_dependencies:
- test: '^0.12.0'
+ dart_style: '^1.0.9'
+ build_runner: ^0.7.11
+ build_test: ^0.10.0
+ build_web_compilers: ^0.3.1
+ test: ^0.12.0
diff --git a/packages/observable/test/list_change_test.dart b/packages/observable/test/list_change_test.dart
index b07c0b8..858b27b 100644
--- a/packages/observable/test/list_change_test.dart
+++ b/packages/observable/test/list_change_test.dart
@@ -20,7 +20,7 @@
var model;
tearDown(() {
- sub.cancel();
+ sub?.cancel();
model = null;
});
@@ -37,7 +37,11 @@
model.add(2);
expect(summary, null);
- return new Future(() => expectChanges(summary, [_delta(1, [], 2)]));
+ return new Future(() {
+ expectChanges(summary, [_delta(1, [], 2)]);
+ expect(summary[0].added, [1, 2]);
+ expect(summary[0].removed, []);
+ });
});
test('List Splice Truncate And Expand With Length', () {
@@ -47,29 +51,28 @@
sub = model.listChanges.listen((r) => summary = r);
model.length = 2;
-
return new Future(() {
expectChanges(summary, [
_delta(2, ['c', 'd', 'e'], 0)
]);
+ expect(summary[0].added, []);
+ expect(summary[0].removed, ['c', 'd', 'e']);
summary = null;
model.length = 5;
}).then(newMicrotask).then((_) {
expectChanges(summary, [_delta(2, [], 3)]);
+ expect(summary[0].added, [null, null, null]);
+ expect(summary[0].removed, []);
});
});
group('List deltas can be applied', () {
applyAndCheckDeltas(model, copy, changes) => changes.then((summary) {
// apply deltas to the copy
- for (var delta in summary) {
- copy.removeRange(delta.index, delta.index + delta.removed.length);
- for (int i = delta.addedCount - 1; i >= 0; i--) {
- copy.insert(delta.index, model[delta.index + i]);
- }
+ for (ListChangeRecord delta in summary) {
+ delta.apply(copy);
}
- // Note: compare strings for easier debugging.
expect('$copy', '$model', reason: 'summary $summary');
});
@@ -87,7 +90,7 @@
});
test('Delete Empty', () {
- var model = toObservable([1]);
+ var model = toObservable(<dynamic>[1]);
var copy = model.toList();
var changes = model.listChanges.first;
diff --git a/packages/observable/test/map_differ_test.dart b/packages/observable/test/map_differ_test.dart
new file mode 100644
index 0000000..7fb9ca9
--- /dev/null
+++ b/packages/observable/test/map_differ_test.dart
@@ -0,0 +1,115 @@
+import 'package:observable/observable.dart';
+import 'package:test/test.dart';
+
+main() {
+ group('$MapDiffer', () {
+ final diff = const MapDiffer<String, String>().diff;
+
+ test('should emit no changes for identical maps', () {
+ final map = new Map<String, String>.fromIterable(
+ new Iterable.generate(10, (i) => '$i'),
+ );
+ expect(diff(map, map), isEmpty);
+ });
+
+ test('should emit no changes for maps with identical content', () {
+ final map1 = new Map<String, String>.fromIterable(
+ new Iterable.generate(10, (i) => '$i'),
+ );
+ final map2 = new Map<String, String>.fromIterable(
+ new Iterable.generate(10, (i) => '$i'),
+ );
+ expect(diff(map1, map2), isEmpty);
+ });
+
+ test('should detect insertions', () {
+ expect(
+ diff({
+ 'key-a': 'value-a',
+ 'key-b': 'value-b',
+ }, {
+ 'key-a': 'value-a',
+ 'key-b': 'value-b',
+ 'key-c': 'value-c',
+ }),
+ [
+ new MapChangeRecord.insert('key-c', 'value-c'),
+ ],
+ );
+ });
+
+ test('should detect removals', () {
+ expect(
+ diff({
+ 'key-a': 'value-a',
+ 'key-b': 'value-b',
+ 'key-c': 'value-c',
+ }, {
+ 'key-a': 'value-a',
+ 'key-b': 'value-b',
+ }),
+ [
+ new MapChangeRecord.remove('key-c', 'value-c'),
+ ],
+ );
+ });
+
+ test('should detect updates', () {
+ expect(
+ diff({
+ 'key-a': 'value-a',
+ 'key-b': 'value-b-old',
+ }, {
+ 'key-a': 'value-a',
+ 'key-b': 'value-b-new',
+ }),
+ [
+ new MapChangeRecord('key-b', 'value-b-old', 'value-b-new'),
+ ],
+ );
+ });
+ });
+
+ group('$MapChangeRecord', () {
+ test('should reply an insertion', () {
+ final map1 = {
+ 'key-a': 'value-a',
+ 'key-b': 'value-b',
+ };
+ final map2 = {
+ 'key-a': 'value-a',
+ 'key-b': 'value-b',
+ 'key-c': 'value-c',
+ };
+ new MapChangeRecord.insert('key-c', 'value-c').apply(map1);
+ expect(map1, map2);
+ });
+
+ test('should replay a removal', () {
+ final map1 = {
+ 'key-a': 'value-a',
+ 'key-b': 'value-b',
+ 'key-c': 'value-c',
+ };
+ final map2 = {
+ 'key-a': 'value-a',
+ 'key-b': 'value-b',
+ };
+ new MapChangeRecord.remove('key-c', 'value-c').apply(map1);
+ expect(map1, map2);
+ });
+
+ test('should replay an update', () {
+ final map1 = {
+ 'key-a': 'value-a',
+ 'key-b': 'value-b-old',
+ };
+ final map2 = {
+ 'key-a': 'value-a',
+ 'key-b': 'value-b-new',
+ };
+ new MapChangeRecord('key-b', 'value-b-old', 'value-b-new').apply(map1);
+ expect(map1, map2);
+ });
+ });
+}
diff --git a/packages/observable/test/observable_list_test.dart b/packages/observable/test/observable_list_test.dart
index 0a29fa3..b999cfd 100644
--- a/packages/observable/test/observable_list_test.dart
+++ b/packages/observable/test/observable_list_test.dart
@@ -65,6 +65,15 @@
});
});
+ test('removeWhere changes length', () {
+ list.add(2);
+ list.removeWhere((e) => e == 2);
+ expect(list, [1, 3]);
+ return new Future(() {
+ expectChanges(changes, [_lengthChange(3, 4), _lengthChange(4, 2)]);
+ });
+ });
+
test('length= changes length', () {
list.length = 5;
expect(list, [1, 2, 3, null, null]);
@@ -266,6 +275,19 @@
});
});
+ test('removeWhere', () {
+ list.removeWhere((e) => e == 3);
+ expect(list, orderedEquals([1, 2, 1, 4]));
+
+ return new Future(() {
+ expectChanges(propRecords, [_lengthChange(6, 4)]);
+ expectChanges(listRecords, [
+ _change(2, removed: [3]),
+ _change(3, removed: [3])
+ ]);
+ });
+ });
+
test('sort', () {
list.sort((x, y) => x - y);
expect(list, orderedEquals([1, 1, 2, 3, 3, 4]));
@@ -311,7 +333,7 @@
});
}
-ObservableList list;
+ObservableList<int> list;
PropertyChangeRecord _lengthChange(int oldValue, int newValue) =>
new PropertyChangeRecord(list, #length, oldValue, newValue);
diff --git a/packages/observable/test/observable_map_test.dart b/packages/observable/test/observable_map_test.dart
index a424ce1..04aeccc 100644
--- a/packages/observable/test/observable_map_test.dart
+++ b/packages/observable/test/observable_map_test.dart
@@ -362,6 +362,21 @@
});
});
});
+
+ group('Updates delegate as a spy', () {
+ Map delegate;
+ ObservableMap map;
+
+ setUp(() {
+ delegate = {};
+ map = new ObservableMap.spy(delegate);
+ });
+
+ test('[]=', () {
+ map['a'] = 42;
+ expect(delegate, {'a': 42});
+ });
+ });
}
_lengthChange(map, int oldValue, int newValue) =>
diff --git a/packages/observable/test/observable_test.dart b/packages/observable/test/observable_test.dart
index b89d654..3a05e2d 100644
--- a/packages/observable/test/observable_test.dart
+++ b/packages/observable/test/observable_test.dart
@@ -9,7 +9,7 @@
import 'observable_test_utils.dart';
-main() => observableTests();
+void main() => observableTests();
void observableTests() {
// Track the subscriptions so we can clean them up in tearDown.
@@ -206,7 +206,7 @@
createModel(int number) => new ObservableSubclass(number);
-class ObservableSubclass<T> extends Observable {
+class ObservableSubclass<T> extends PropertyChangeNotifier {
ObservableSubclass([T initialValue]) : _value = initialValue;
T get value => _value;
@@ -218,5 +218,6 @@
T _value;
+ @override
String toString() => '#<$runtimeType value: $value>';
}
diff --git a/packages/observable/test/observable_test_utils.dart b/packages/observable/test/observable_test_utils.dart
index 91d4d8f..990cc86 100644
--- a/packages/observable/test/observable_test_utils.dart
+++ b/packages/observable/test/observable_test_utils.dart
@@ -13,8 +13,6 @@
/// to happen in the next microtask:
///
/// future.then(newMicrotask).then(...)
-///
-/// Uses [mu].
newMicrotask(_) => new Future.value();
// TODO(jmesserly): use matchers when we have a way to compare ChangeRecords.
diff --git a/packages/observable/tool/travis.sh b/packages/observable/tool/travis.sh
new file mode 100755
index 0000000..0e584b9
--- /dev/null
+++ b/packages/observable/tool/travis.sh
@@ -0,0 +1,65 @@
+# Copyright 2018 the Dart project authors.
+#
+# 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.
+
+#!/bin/bash
+
+if [ "$#" == "0" ]; then
+ echo -e '\033[31mAt least one task argument must be provided!\033[0m'
+ exit 1
+fi
+
+EXIT_CODE=0
+
+while (( "$#" )); do
+ TASK=$1
+ case $TASK in
+ dartfmt) echo
+ echo -e '\033[1mTASK: dartfmt\033[22m'
+ echo -e 'dartfmt -n --set-exit-if-changed .'
+ dartfmt -n --set-exit-if-changed . || EXIT_CODE=$?
+ ;;
+ dartanalyzer) echo
+ echo -e '\033[1mTASK: dartanalyzer\033[22m'
+ echo -e 'dartanalyzer --fatal-warnings .'
+ dartanalyzer --fatal-warnings . || EXIT_CODE=$?
+ ;;
+ vm_test) echo
+ echo -e '\033[1mTASK: vm_test\033[22m'
+ echo -e 'pub run test -P travis -p vm -x requires-dart2'
+ pub run test -p vm || EXIT_CODE=$?
+ ;;
+ dartdevc_build) echo
+ echo -e '\033[1mTASK: build\033[22m'
+ echo -e 'pub run build_runner build --fail-on-severe'
+ pub run build_runner build --fail-on-severe || EXIT_CODE=$?
+ ;;
+ dartdevc_test) echo
+ echo -e '\033[1mTASK: dartdevc_test\033[22m'
+ echo -e 'pub run build_runner test -- -P travis -p chrome'
+ pub run build_runner test -- -p chrome || EXIT_CODE=$?
+ ;;
+ dart2js_test) echo
+ echo -e '\033[1mTASK: dart2js_test\033[22m'
+ echo -e 'pub run test -P travis -p chrome -x requires-dart2'
+ pub run test -p chrome || EXIT_CODE=$?
+ ;;
+ *) echo -e "\033[31mNot expecting TASK '${TASK}'. Error!\033[0m"
+ EXIT_CODE=1
+ ;;
+ esac
+
+ shift
+done
+
+exit $EXIT_CODE
diff --git a/packages/path/.gitignore b/packages/path/.gitignore
index 7dbf035..813a31e 100644
--- a/packages/path/.gitignore
+++ b/packages/path/.gitignore
@@ -1,6 +1,7 @@
# Don’t commit the following directories created by pub.
.buildlog
.pub/
+.dart_tool/
build/
packages
.packages
@@ -12,4 +13,4 @@
*.js.map
# Include when developing application packages.
-pubspec.lock
\ No newline at end of file
+pubspec.lock
diff --git a/packages/path/.travis.yml b/packages/path/.travis.yml
index 63f5e5e..fc8e768 100644
--- a/packages/path/.travis.yml
+++ b/packages/path/.travis.yml
@@ -2,16 +2,11 @@
dart:
- dev
- - stable
dart_task:
- test
- - dartanalyzer
-
-matrix:
- include:
- - dart: dev
- dart_task: dartfmt
+ - dartfmt
+ - dartanalyze
# Only building master means that we don't run two builds for each pull request.
branches:
diff --git a/packages/path/CHANGELOG.md b/packages/path/CHANGELOG.md
index e23e380..42731ad 100644
--- a/packages/path/CHANGELOG.md
+++ b/packages/path/CHANGELOG.md
@@ -1,3 +1,13 @@
+## 1.6.1
+
+* Drop the `retype` implementation for compatibility with the latest SDK.
+
+## 1.6.0
+
+* Add a `PathMap` class that uses path equality for its keys.
+
+* Add a `PathSet` class that uses path equality for its contents.
+
## 1.5.1
* Fix a number of bugs that occurred when the current working directory was `/`
diff --git a/packages/path/lib/path.dart b/packages/path/lib/path.dart
index 9885859..ed70f9d 100644
--- a/packages/path/lib/path.dart
+++ b/packages/path/lib/path.dart
@@ -49,6 +49,8 @@
export 'src/context.dart' hide createInternal;
export 'src/path_exception.dart';
+export 'src/path_map.dart';
+export 'src/path_set.dart';
export 'src/style.dart';
/// A default context for manipulating POSIX paths.
diff --git a/packages/path/lib/src/path_map.dart b/packages/path/lib/src/path_map.dart
new file mode 100644
index 0000000..53205ad
--- /dev/null
+++ b/packages/path/lib/src/path_map.dart
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import '../path.dart' as p;
+
+/// A map whose keys are paths, compared using [equals] and [hash].
+class PathMap<V> extends MapView<String, V> {
+ /// Creates an empty [PathMap] whose keys are compared using `context.equals`
+ /// and `context.hash`.
+ ///
+ /// The [context] defaults to the current path context.
+ PathMap({p.Context context}) : super(_create(context));
+
+ /// Creates a [PathMap] with the same keys and values as [other] whose keys
+ /// are compared using `context.equals` and `context.hash`.
+ ///
+ /// The [context] defaults to the current path context. If multiple keys in
+ /// [other] represent the same logical path, the last key's value will be
+ /// used.
+ PathMap.of(Map<String, V> other, {p.Context context})
+ : super(_create(context)..addAll(other));
+
+ /// Creates a map that uses [context] for equality and hashing.
+ static Map<String, V> _create<V>(p.Context context) {
+ context ??= p.context;
+ return new LinkedHashMap(
+ equals: (path1, path2) {
+ if (path1 == null) return path2 == null;
+ if (path2 == null) return false;
+ return context.equals(path1, path2);
+ },
+ hashCode: (path) => path == null ? 0 : context.hash(path),
+ isValidKey: (path) => path is String || path == null);
+ }
+}
diff --git a/packages/path/lib/src/path_set.dart b/packages/path/lib/src/path_set.dart
new file mode 100644
index 0000000..e3b9461
--- /dev/null
+++ b/packages/path/lib/src/path_set.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:collection';
+
+import '../path.dart' as p;
+
+/// A set containing paths, compared using [equals] and [hash].
+class PathSet extends IterableBase<String> implements Set<String> {
+ /// The set to which we forward implementation methods.
+ final Set<String> _inner;
+
+ /// Creates an empty [PathSet] whose contents are compared using
+ /// `context.equals` and `context.hash`.
+ ///
+ /// The [context] defaults to the current path context.
+ PathSet({p.Context context}) : _inner = _create(context);
+
+ /// Creates a [PathSet] with the same contents as [other] whose elements are
+ /// compared using `context.equals` and `context.hash`.
+ ///
+ /// The [context] defaults to the current path context. If multiple elements
+ /// in [other] represent the same logical path, the first value will be
+ /// used.
+ PathSet.of(Iterable<String> other, {p.Context context})
+ : _inner = _create(context)..addAll(other);
+
+ /// Creates a set that uses [context] for equality and hashing.
+ static Set<String> _create(p.Context context) {
+ context ??= p.context;
+ return new LinkedHashSet(
+ equals: (path1, path2) {
+ if (path1 == null) return path2 == null;
+ if (path2 == null) return false;
+ return context.equals(path1, path2);
+ },
+ hashCode: (path) => path == null ? 0 : context.hash(path),
+ isValidKey: (path) => path is String || path == null);
+ }
+
+ // Normally we'd use DelegatingSetView from the collection package to
+ // implement these, but we want to avoid adding dependencies from path because
+ // it's so widely used that even brief version skew can be very painful.
+
+ Iterator<String> get iterator => _inner.iterator;
+
+ int get length => _inner.length;
+
+ bool add(String value) => _inner.add(value);
+
+ void addAll(Iterable<String> elements) => _inner.addAll(elements);
+
+ Set<T> cast<T>() => _inner.cast<T>();
+
+ void clear() => _inner.clear();
+
+ bool contains(Object other) => _inner.contains(other);
+
+ bool containsAll(Iterable<Object> other) => _inner.containsAll(other);
+
+ Set<String> difference(Set<Object> other) => _inner.difference(other);
+
+ Set<String> intersection(Set<Object> other) => _inner.intersection(other);
+
+ String lookup(Object element) => _inner.lookup(element);
+
+ bool remove(Object value) => _inner.remove(value);
+
+ void removeAll(Iterable<Object> elements) => _inner.removeAll(elements);
+
+ void removeWhere(bool test(String element)) => _inner.removeWhere(test);
+
+ void retainAll(Iterable<Object> elements) => _inner.retainAll(elements);
+
+ void retainWhere(bool test(String element)) => _inner.retainWhere(test);
+
+ Set<String> union(Set<String> other) => _inner.union(other);
+
+ Set<String> toSet() => _inner.toSet();
+}
diff --git a/packages/path/pubspec.yaml b/packages/path/pubspec.yaml
index 30a8dff..ece564f 100644
--- a/packages/path/pubspec.yaml
+++ b/packages/path/pubspec.yaml
@@ -1,5 +1,5 @@
name: path
-version: 1.5.1
+version: 1.6.1
author: Dart Team <misc@dartlang.org>
description: >
A string-based path manipulation library. All of the path operations you know
@@ -9,4 +9,4 @@
dev_dependencies:
test: ">=0.12.0 <0.13.0"
environment:
- sdk: ">=1.0.0 <2.0.0"
+ sdk: ">=2.0.0-dev.62.0 <2.0.0"
diff --git a/packages/path/test/path_map_test.dart b/packages/path/test/path_map_test.dart
new file mode 100644
index 0000000..ce025db
--- /dev/null
+++ b/packages/path/test/path_map_test.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import 'package:path/path.dart';
+
+void main() {
+ group("considers equal", () {
+ test("two identical paths", () {
+ var map = new PathMap<int>();
+ map[join("foo", "bar")] = 1;
+ map[join("foo", "bar")] = 2;
+ expect(map, hasLength(1));
+ expect(map, containsPair(join("foo", "bar"), 2));
+ });
+
+ test("two logically equivalent paths", () {
+ var map = new PathMap<int>();
+ map["foo"] = 1;
+ map[absolute("foo")] = 2;
+ expect(map, hasLength(1));
+ expect(map, containsPair("foo", 2));
+ expect(map, containsPair(absolute("foo"), 2));
+ });
+
+ test("two nulls", () {
+ var map = new PathMap<int>();
+ map[null] = 1;
+ map[null] = 2;
+ expect(map, hasLength(1));
+ expect(map, containsPair(null, 2));
+ });
+ });
+
+ group("considers unequal", () {
+ test("two distinct paths", () {
+ var map = new PathMap<int>();
+ map["foo"] = 1;
+ map["bar"] = 2;
+ expect(map, hasLength(2));
+ expect(map, containsPair("foo", 1));
+ expect(map, containsPair("bar", 2));
+ });
+
+ test("a path and null", () {
+ var map = new PathMap<int>();
+ map["foo"] = 1;
+ map[null] = 2;
+ expect(map, hasLength(2));
+ expect(map, containsPair("foo", 1));
+ expect(map, containsPair(null, 2));
+ });
+ });
+
+ test("uses the custom context", () {
+ var map = new PathMap<int>(context: windows);
+ map["FOO"] = 1;
+ map["foo"] = 2;
+ expect(map, hasLength(1));
+ expect(map, containsPair("fOo", 2));
+ });
+
+ group(".of()", () {
+ test("copies the existing map's keys", () {
+ var map = new PathMap.of({"foo": 1, "bar": 2});
+ expect(map, hasLength(2));
+ expect(map, containsPair("foo", 1));
+ expect(map, containsPair("bar", 2));
+ });
+
+ test("uses the second value in the case of duplicates", () {
+ var map = new PathMap.of({"foo": 1, absolute("foo"): 2});
+ expect(map, hasLength(1));
+ expect(map, containsPair("foo", 2));
+ expect(map, containsPair(absolute("foo"), 2));
+ });
+ });
+}
diff --git a/packages/path/test/path_set_test.dart b/packages/path/test/path_set_test.dart
new file mode 100644
index 0000000..884c184
--- /dev/null
+++ b/packages/path/test/path_set_test.dart
@@ -0,0 +1,81 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import 'package:path/path.dart';
+
+void main() {
+ group("considers equal", () {
+ test("two identical paths", () {
+ var set = new PathSet();
+ expect(set.add(join("foo", "bar")), isTrue);
+ expect(set.add(join("foo", "bar")), isFalse);
+ expect(set, hasLength(1));
+ expect(set, contains(join("foo", "bar")));
+ });
+
+ test("two logically equivalent paths", () {
+ var set = new PathSet();
+ expect(set.add("foo"), isTrue);
+ expect(set.add(absolute("foo")), isFalse);
+ expect(set, hasLength(1));
+ expect(set, contains("foo"));
+ expect(set, contains(absolute("foo")));
+ });
+
+ test("two nulls", () {
+ var set = new PathSet();
+ expect(set.add(null), isTrue);
+ expect(set.add(null), isFalse);
+ expect(set, hasLength(1));
+ expect(set, contains(null));
+ });
+ });
+
+ group("considers unequal", () {
+ test("two distinct paths", () {
+ var set = new PathSet();
+ expect(set.add("foo"), isTrue);
+ expect(set.add("bar"), isTrue);
+ expect(set, hasLength(2));
+ expect(set, contains("foo"));
+ expect(set, contains("bar"));
+ });
+
+ test("a path and null", () {
+ var set = new PathSet();
+ expect(set.add("foo"), isTrue);
+ expect(set.add(null), isTrue);
+ expect(set, hasLength(2));
+ expect(set, contains("foo"));
+ expect(set, contains(null));
+ });
+ });
+
+ test("uses the custom context", () {
+ var set = new PathSet(context: windows);
+ expect(set.add("FOO"), isTrue);
+ expect(set.add("foo"), isFalse);
+ expect(set, hasLength(1));
+ expect(set, contains("fOo"));
+ });
+
+ group(".of()", () {
+ test("copies the existing set's keys", () {
+ var set = new PathSet.of(["foo", "bar"]);
+ expect(set, hasLength(2));
+ expect(set, contains("foo"));
+ expect(set, contains("bar"));
+ });
+
+ test("uses the first value in the case of duplicates", () {
+ var set = new PathSet.of(["foo", absolute("foo")]);
+ expect(set, hasLength(1));
+ expect(set, contains("foo"));
+ expect(set, contains(absolute("foo")));
+ expect(set.first, "foo");
+ });
+ });
+}
diff --git a/packages/usage/.gitignore b/packages/usage/.gitignore
index a9df6e6..fd4bf9d 100644
--- a/packages/usage/.gitignore
+++ b/packages/usage/.gitignore
@@ -1,6 +1,7 @@
.packages
.idea/
.pub/
+.dart_tool/
build/
doc/api/
pubspec.lock
diff --git a/packages/usage/PATENTS b/packages/usage/PATENTS
deleted file mode 100644
index 36a3874..0000000
--- a/packages/usage/PATENTS
+++ /dev/null
@@ -1,24 +0,0 @@
-Additional IP Rights Grant (Patents)
-
-"This implementation" means the copyrightable works distributed by
-Google as part of the Dart Project.
-
-Google hereby grants to you a perpetual, worldwide, non-exclusive,
-no-charge, royalty-free, irrevocable (except as stated in this
-section) patent license to make, have made, use, offer to sell, sell,
-import, transfer, and otherwise run, modify and propagate the contents
-of this implementation of Dart, where such license applies only to
-those patent claims, both currently owned by Google and acquired in
-the future, licensable by Google that are necessarily infringed by
-this implementation of Dart. This grant does not include claims that
-would be infringed only as a consequence of further modification of
-this implementation. If you or your agent or exclusive licensee
-institute or order or agree to the institution of patent litigation
-against any entity (including a cross-claim or counterclaim in a
-lawsuit) alleging that this implementation of Dart or any code
-incorporated within this implementation of Dart constitutes direct or
-contributory patent infringement, or inducement of patent
-infringement, then any patent rights granted to you under this License
-for this implementation of Dart shall terminate as of the date such
-litigation is filed.
-
diff --git a/packages/usage/analysis_options.yaml b/packages/usage/analysis_options.yaml
index 2ef22c9..d7bffe1 100644
--- a/packages/usage/analysis_options.yaml
+++ b/packages/usage/analysis_options.yaml
@@ -3,6 +3,29 @@
linter:
rules:
- annotate_overrides
+ - avoid_empty_else
+ - avoid_init_to_null
+ - avoid_return_types_on_setters
+ - await_only_futures
+ - camel_case_types
+ - control_flow_in_finally
- directives_ordering
+ - empty_catches
+ - empty_constructor_bodies
- empty_constructor_bodies
- empty_statements
+ - empty_statements
+ - implementation_imports
+ - library_names
+ - library_prefixes
+ - non_constant_identifier_names
+ - only_throw_errors
+ - prefer_final_fields
+ - prefer_is_not_empty
+ - prefer_single_quotes
+ - slash_for_doc_comments
+ - test_types_in_equals
+ - throw_in_finally
+ - type_init_formals
+ - unrelated_type_equality_checks
+ - valid_regexps
diff --git a/packages/usage/changelog.md b/packages/usage/changelog.md
index 351cb38..e43adb3 100644
--- a/packages/usage/changelog.md
+++ b/packages/usage/changelog.md
@@ -1,5 +1,9 @@
# Changelog
+## 3.4.0
+- bump our minimum SDK constraint to `>=2.0.0-dev.30`
+- change to using non-deprecated dart:convert constants
+
## 3.3.0
- added a `close()` method to the `Analytics` class
- change our minimum SDK from `1.24.0-dev` to `1.24.0` stable
diff --git a/packages/usage/example/ga.dart b/packages/usage/example/ga.dart
index ad64249..3df1573 100644
--- a/packages/usage/example/ga.dart
+++ b/packages/usage/example/ga.dart
@@ -8,16 +8,16 @@
import 'package:usage/usage_io.dart';
main(List args) async {
- final String DEFAULT_UA = 'UA-55029513-1';
+ final String defaultUA = 'UA-55029513-1';
if (args.isEmpty) {
print('usage: dart ga <GA tracking ID>');
- print('pinging default UA value (${DEFAULT_UA})');
+ print('pinging default UA value (${defaultUA})');
} else {
print('pinging ${args.first}');
}
- String ua = args.isEmpty ? DEFAULT_UA : args.first;
+ String ua = args.isEmpty ? defaultUA : args.first;
Analytics ga = new AnalyticsIO(ua, 'ga_test', '3.0');
diff --git a/packages/usage/lib/src/usage_impl.dart b/packages/usage/lib/src/usage_impl.dart
index df529e3..7a8cf21 100644
--- a/packages/usage/lib/src/usage_impl.dart
+++ b/packages/usage/lib/src/usage_impl.dart
@@ -12,18 +12,16 @@
// &foo=bar
return map.keys.map((key) {
String value = '${map[key]}';
- return "${key}=${Uri.encodeComponent(value)}";
+ return '${key}=${Uri.encodeComponent(value)}';
}).join('&');
}
-/**
- * A throttling algorithm. This models the throttling after a bucket with
- * water dripping into it at the rate of 1 drop per second. If the bucket has
- * water when an operation is requested, 1 drop of water is removed and the
- * operation is performed. If not the operation is skipped. This algorithm
- * lets operations be performed in bursts without throttling, but holds the
- * overall average rate of operations to 1 per second.
- */
+/// A throttling algorithm. This models the throttling after a bucket with
+/// water dripping into it at the rate of 1 drop per second. If the bucket has
+/// water when an operation is requested, 1 drop of water is removed and the
+/// operation is performed. If not the operation is skipped. This algorithm
+/// lets operations be performed in bursts without throttling, but holds the
+/// overall average rate of operations to 1 per second.
class ThrottlingBucket {
final int startingCount;
int drops;
@@ -80,7 +78,7 @@
String _url;
- StreamController<Map<String, dynamic>> _sendController =
+ final StreamController<Map<String, dynamic>> _sendController =
new StreamController.broadcast(sync: true);
AnalyticsImpl(this.trackingId, this.properties, this.postHandler,
@@ -220,21 +218,17 @@
@override
String get clientId => properties['clientId'] ??= new Uuid().generateV4();
- /**
- * Send raw data to analytics. Callers should generally use one of the typed
- * methods (`sendScreenView`, `sendEvent`, ...).
- *
- * Valid values for [hitType] are: 'pageview', 'screenview', 'event',
- * 'transaction', 'item', 'social', 'exception', and 'timing'.
- */
+ /// Send raw data to analytics. Callers should generally use one of the typed
+ /// methods (`sendScreenView`, `sendEvent`, ...).
+ ///
+ /// Valid values for [hitType] are: 'pageview', 'screenview', 'event',
+ /// 'transaction', 'item', 'social', 'exception', and 'timing'.
Future sendRaw(String hitType, Map<String, dynamic> args) {
return _sendPayload(hitType, args);
}
- /**
- * Valid values for [hitType] are: 'pageview', 'screenview', 'event',
- * 'transaction', 'item', 'social', 'exception', and 'timing'.
- */
+ /// Valid values for [hitType] are: 'pageview', 'screenview', 'event',
+ /// 'transaction', 'item', 'social', 'exception', and 'timing'.
Future _sendPayload(String hitType, Map<String, dynamic> args) {
if (!enabled) return new Future.value();
@@ -262,14 +256,13 @@
}
}
-/**
- * A persistent key/value store. An [AnalyticsImpl] instance expects to have one
- * of these injected into it. There are default implementations for `dart:io`
- * and `dart:html` clients.
- *
- * The [name] parameter is used to uniquely store these properties on disk /
- * persistent storage.
- */
+/// A persistent key/value store. An [AnalyticsImpl] instance expects to have
+/// one of these injected into it.
+///
+/// There are default implementations for `dart:io` and `dart:html` clients.
+///
+/// The [name] parameter is used to uniquely store these properties on disk /
+/// persistent storage.
abstract class PersistentProperties {
final String name;
@@ -283,15 +276,15 @@
void syncSettings();
}
-/**
- * A utility class to perform HTTP POSTs. An [AnalyticsImpl] instance expects to
- * have one of these injected into it. There are default implementations for
- * `dart:io` and `dart:html` clients.
- *
- * The POST information should be sent on a best-effort basis. The `Future` from
- * [sendPost] should complete when the operation is finished, but failures to
- * send the information should be silent.
- */
+/// A utility class to perform HTTP POSTs.
+///
+/// An [AnalyticsImpl] instance expects to have one of these injected into it.
+/// There are default implementations for `dart:io` and `dart:html` clients.
+///
+/// The POST information should be sent on a best-effort basis.
+///
+/// The `Future` from [sendPost] should complete when the operation is finished,
+/// but failures to send the information should be silent.
abstract class PostHandler {
Future sendPost(String url, Map<String, dynamic> parameters);
diff --git a/packages/usage/lib/src/usage_impl_html.dart b/packages/usage/lib/src/usage_impl_html.dart
index c15cde6..010b508 100644
--- a/packages/usage/lib/src/usage_impl_html.dart
+++ b/packages/usage/lib/src/usage_impl_html.dart
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
-import 'dart:convert' show JSON;
+import 'dart:convert' show jsonEncode, jsonDecode;
import 'dart:html';
import 'usage_impl.dart';
@@ -64,7 +64,7 @@
HtmlPersistentProperties(String name) : super(name) {
String str = window.localStorage[name];
if (str == null || str.isEmpty) str = '{}';
- _map = JSON.decode(str);
+ _map = jsonDecode(str);
}
@override
@@ -78,7 +78,7 @@
_map[key] = value;
}
- window.localStorage[name] = JSON.encode(_map);
+ window.localStorage[name] = jsonEncode(_map);
}
@override
diff --git a/packages/usage/lib/src/usage_impl_io.dart b/packages/usage/lib/src/usage_impl_io.dart
index 6e82d41..53aa967 100644
--- a/packages/usage/lib/src/usage_impl_io.dart
+++ b/packages/usage/lib/src/usage_impl_io.dart
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
-import 'dart:convert' show JSON, JsonEncoder;
+import 'dart:convert' show jsonDecode, JsonEncoder;
import 'dart:io';
import 'package:path/path.dart' as path;
@@ -56,7 +56,7 @@
} else {
// Dart/1.8.0 (macos; macos; macos; en_US)
String os = Platform.operatingSystem;
- return "Dart/${getDartVersion()} (${os}; ${os}; ${os}; ${locale})";
+ return 'Dart/${getDartVersion()} (${os}; ${os}; ${os}; ${locale})';
}
}
@@ -153,7 +153,7 @@
try {
String contents = _file.readAsStringSync();
if (contents.isEmpty) contents = '{}';
- _map = JSON.decode(contents);
+ _map = jsonDecode(contents);
} catch (_) {
_map = {};
}
diff --git a/packages/usage/lib/usage.dart b/packages/usage/lib/usage.dart
index e79701c..0622335 100644
--- a/packages/usage/lib/usage.dart
+++ b/packages/usage/lib/usage.dart
@@ -2,26 +2,24 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-/**
- * `usage` is a wrapper around Google Analytics for both command-line apps
- * and web apps.
- *
- * In order to use this library as a web app, import the `analytics_html.dart`
- * library and instantiate the [AnalyticsHtml] class.
- *
- * In order to use this library as a command-line app, import the
- * `analytics_io.dart` library and instantiate the [AnalyticsIO] class.
- *
- * For both classes, you need to provide a Google Analytics tracking ID, the
- * application name, and the application version.
- *
- * Your application should provide an opt-in option for the user. If they
- * opt-in, set the [optIn] field to `true`. This setting will persist across
- * sessions automatically.
- *
- * For more information, please see the Google Analytics Measurement Protocol
- * [Policy](https://developers.google.com/analytics/devguides/collection/protocol/policy).
- */
+/// `usage` is a wrapper around Google Analytics for both command-line apps
+/// and web apps.
+///
+/// In order to use this library as a web app, import the `analytics_html.dart`
+/// library and instantiate the [AnalyticsHtml] class.
+///
+/// In order to use this library as a command-line app, import the
+/// `analytics_io.dart` library and instantiate the [AnalyticsIO] class.
+///
+/// For both classes, you need to provide a Google Analytics tracking ID, the
+/// application name, and the application version.
+///
+/// Your application should provide an opt-in option for the user. If they
+/// opt-in, set the [optIn] field to `true`. This setting will persist across
+/// sessions automatically.
+///
+/// For more information, please see the Google Analytics Measurement Protocol
+/// [Policy](https://developers.google.com/analytics/devguides/collection/protocol/policy).
library usage;
import 'dart:async';
@@ -32,19 +30,17 @@
// Match multiple tabs or spaces.
final RegExp _tabOrSpaceRegex = new RegExp(r'[\t ]+');
-/**
- * An interface to a Google Analytics session. [AnalyticsHtml] and [AnalyticsIO]
- * are concrete implementations of this interface. [AnalyticsMock] can be used
- * for testing or for some variants of an opt-in workflow.
- *
- * The analytics information is sent on a best-effort basis. So, failures to
- * send the GA information will not result in errors from the asynchronous
- * `send` methods.
- */
+/// An interface to a Google Analytics session.
+///
+/// [AnalyticsHtml] and [AnalyticsIO] are concrete implementations of this
+/// interface. [AnalyticsMock] can be used for testing or for some variants of
+/// an opt-in workflow.
+///
+/// The analytics information is sent on a best-effort basis. So, failures to
+/// send the GA information will not result in errors from the asynchronous
+/// `send` methods.
abstract class Analytics {
- /**
- * Tracking ID / Property ID.
- */
+ /// Tracking ID / Property ID.
String get trackingId;
/// The application name.
@@ -53,120 +49,93 @@
/// The application version.
String get applicationVersion;
- /**
- * Is this the first time the tool has run?
- */
+ /// Is this the first time the tool has run?
bool get firstRun;
- /**
- * Whether the [Analytics] instance is configured in an opt-in or opt-out manner.
- */
+ /// Whether the [Analytics] instance is configured in an opt-in or opt-out
+ /// manner.
AnalyticsOpt analyticsOpt = AnalyticsOpt.optOut;
- /**
- * Will analytics data be sent.
- */
+ /// Will analytics data be sent.
bool get enabled;
- /**
- * Enable or disable sending of analytics data.
- */
+ /// Enable or disable sending of analytics data.
set enabled(bool value);
- /**
- * Anonymous client ID in UUID v4 format.
- *
- * The value is randomly-generated and should be reasonably stable for the
- * computer sending analytics data.
- */
+ /// Anonymous client ID in UUID v4 format.
+ ///
+ /// The value is randomly-generated and should be reasonably stable for the
+ /// computer sending analytics data.
String get clientId;
- /**
- * Sends a screen view hit to Google Analytics.
- *
- * [parameters] can be any analytics key/value pair. Useful
- * for custom dimensions, etc.
- */
+ /// Sends a screen view hit to Google Analytics.
+ ///
+ /// [parameters] can be any analytics key/value pair. Useful
+ /// for custom dimensions, etc.
Future sendScreenView(String viewName, {Map<String, String> parameters});
- /**
- * Sends an Event hit to Google Analytics. [label] specifies the event label.
- * [value] specifies the event value. Values must be non-negative.
- *
- * [parameters] can be any analytics key/value pair. Useful
- * for custom dimensions, etc.
- */
+ /// Sends an Event hit to Google Analytics. [label] specifies the event label.
+ /// [value] specifies the event value. Values must be non-negative.
+ ///
+ /// [parameters] can be any analytics key/value pair. Useful
+ /// for custom dimensions, etc.
Future sendEvent(String category, String action,
{String label, int value, Map<String, String> parameters});
- /**
- * Sends a Social hit to Google Analytics. [network] specifies the social
- * network, for example Facebook or Google Plus. [action] specifies the social
- * interaction action. For example on Google Plus when a user clicks the +1
- * button, the social action is 'plus'. [target] specifies the target of a
- * social interaction. This value is typically a URL but can be any text.
- */
+ /// Sends a Social hit to Google Analytics.
+ ///
+ /// [network] specifies the social network, for example Facebook or Google
+ /// Plus. [action] specifies the social interaction action. For example on
+ /// Google Plus when a user clicks the +1 button, the social action is 'plus'.
+ /// [target] specifies the target of a
+ /// social interaction. This value is typically a URL but can be any text.
Future sendSocial(String network, String action, String target);
- /**
- * Sends a Timing hit to Google Analytics. [variableName] specifies the
- * variable name of the timing. [time] specifies the user timing value (in
- * milliseconds). [category] specifies the category of the timing. [label]
- * specifies the label of the timing.
- */
+ /// Sends a Timing hit to Google Analytics. [variableName] specifies the
+ /// variable name of the timing. [time] specifies the user timing value (in
+ /// milliseconds). [category] specifies the category of the timing. [label]
+ /// specifies the label of the timing.
Future sendTiming(String variableName, int time,
{String category, String label});
- /**
- * Start a timer. The time won't be calculated, and the analytics information
- * sent, until the [AnalyticsTimer.finish] method is called.
- */
+ /// Start a timer. The time won't be calculated, and the analytics information
+ /// sent, until the [AnalyticsTimer.finish] method is called.
AnalyticsTimer startTimer(String variableName,
{String category, String label});
- /**
- * In order to avoid sending any personally identifying information, the
- * [description] field must not contain the exception message. In addition,
- * only the first 100 chars of the description will be sent.
- */
+ /// In order to avoid sending any personally identifying information, the
+ /// [description] field must not contain the exception message. In addition,
+ /// only the first 100 chars of the description will be sent.
Future sendException(String description, {bool fatal});
- /**
- * Gets a session variable value.
- */
+ /// Gets a session variable value.
dynamic getSessionValue(String param);
- /**
- * Sets a session variable value. The value is persistent for the life of the
- * [Analytics] instance. This variable will be sent in with every analytics
- * hit. A list of valid variable names can be found here:
- * https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters.
- */
+ /// Sets a session variable value. The value is persistent for the life of the
+ /// [Analytics] instance. This variable will be sent in with every analytics
+ /// hit. A list of valid variable names can be found here:
+ /// https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters.
void setSessionValue(String param, dynamic value);
- /**
- * Fires events when the usage library sends any data over the network. This
- * will not fire if analytics has been disabled or if the throttling algorithm
- * has been engaged.
- *
- * This method is public to allow library clients to more easily test their
- * analytics implementations.
- */
+ /// Fires events when the usage library sends any data over the network. This
+ /// will not fire if analytics has been disabled or if the throttling
+ /// algorithm has been engaged.
+ ///
+ /// This method is public to allow library clients to more easily test their
+ /// analytics implementations.
Stream<Map<String, dynamic>> get onSend;
- /**
- * Wait for all of the outstanding analytics pings to complete. The returned
- * `Future` will always complete without errors. You can pass in an optional
- * `Duration` to specify to only wait for a certain amount of time.
- *
- * This method is particularly useful for command-line clients. Outstanding
- * I/O requests will cause the VM to delay terminating the process. Generally,
- * users won't want their CLI app to pause at the end of the process waiting
- * for Google analytics requests to complete. This method allows CLI apps to
- * delay for a short time waiting for GA requests to complete, and then do
- * something like call `dart:io`'s `exit()` explicitly themselves (or the
- * [close] method below).
- */
+ /// Wait for all of the outstanding analytics pings to complete. The returned
+ /// `Future` will always complete without errors. You can pass in an optional
+ /// `Duration` to specify to only wait for a certain amount of time.
+ ///
+ /// This method is particularly useful for command-line clients. Outstanding
+ /// I/O requests will cause the VM to delay terminating the process.
+ /// Generally, users won't want their CLI app to pause at the end of the
+ /// process waiting for Google analytics requests to complete. This method
+ /// allows CLI apps to delay for a short time waiting for GA requests to
+ /// complete, and then do something like call `dart:io`'s `exit()` explicitly
+ /// themselves (or the [close] method below).
Future waitForLastPing({Duration timeout});
/// Free any used resources.
@@ -176,21 +145,15 @@
}
enum AnalyticsOpt {
- /**
- * Users must opt-in before any analytics data is sent.
- */
+ /// Users must opt-in before any analytics data is sent.
optIn,
- /**
- * Users must opt-out for analytics data to not be sent.
- */
+ /// Users must opt-out for analytics data to not be sent.
optOut
}
-/**
- * An object, returned by [Analytics.startTimer], that is used to measure an
- * asynchronous process.
- */
+/// An object, returned by [Analytics.startTimer], that is used to measure an
+/// asynchronous process.
class AnalyticsTimer {
final Analytics analytics;
final String variableName;
@@ -213,10 +176,8 @@
}
}
- /**
- * Finish the timer, calculate the elapsed time, and send the information to
- * analytics. Once this is called, any future invocations are no-ops.
- */
+ /// Finish the timer, calculate the elapsed time, and send the information to
+ /// analytics. Once this is called, any future invocations are no-ops.
Future finish() {
if (_endMillis != null) return new Future.value();
@@ -226,10 +187,8 @@
}
}
-/**
- * A no-op implementation of the [Analytics] class. This can be used as a
- * stand-in for that will never ping the GA server, or as a mock in test code.
- */
+/// A no-op implementation of the [Analytics] class. This can be used as a
+/// stand-in for that will never ping the GA server, or as a mock in test code.
class AnalyticsMock implements Analytics {
@override
String get trackingId => 'UA-0';
@@ -240,16 +199,12 @@
final bool logCalls;
- /**
- * Events are never added to this controller for the mock implementation.
- */
- StreamController<Map<String, dynamic>> _sendController =
+ /// Events are never added to this controller for the mock implementation.
+ final StreamController<Map<String, dynamic>> _sendController =
new StreamController.broadcast();
- /**
- * Create a new [AnalyticsMock]. If [logCalls] is true, all calls will be
- * logged to stdout.
- */
+ /// Create a new [AnalyticsMock]. If [logCalls] is true, all calls will be
+ /// logged to stdout.
AnalyticsMock([this.logCalls = false]);
@override
@@ -331,16 +286,14 @@
}
}
-/**
- * Sanitize a stacktrace. This will shorten file paths in order to remove any
- * PII that may be contained in the full file path. For example, this will
- * shorten `file:///Users/foobar/tmp/error.dart` to `error.dart`.
- *
- * If [shorten] is `true`, this method will also attempt to compress the text
- * of the stacktrace. GA has a 100 char limit on the text that can be sent for
- * an exception. This will try and make those first 100 chars contain
- * information useful to debugging the issue.
- */
+/// Sanitize a stacktrace. This will shorten file paths in order to remove any
+/// PII that may be contained in the full file path. For example, this will
+/// shorten `file:///Users/foobar/tmp/error.dart` to `error.dart`.
+///
+/// If [shorten] is `true`, this method will also attempt to compress the text
+/// of the stacktrace. GA has a 100 char limit on the text that can be sent for
+/// an exception. This will try and make those first 100 chars contain
+/// information useful to debugging the issue.
String sanitizeStacktrace(dynamic st, {bool shorten: true}) {
String str = '${st}';
diff --git a/packages/usage/pubspec.yaml b/packages/usage/pubspec.yaml
index 564ba2e..b6f0f6e 100644
--- a/packages/usage/pubspec.yaml
+++ b/packages/usage/pubspec.yaml
@@ -3,13 +3,13 @@
# BSD-style license that can be found in the LICENSE file.
name: usage
-version: 3.3.0
+version: 3.4.0
description: A Google Analytics wrapper for both command-line, web, and Flutter apps.
homepage: https://github.com/dart-lang/usage
author: Dart Team <misc@dartlang.org>
environment:
- sdk: '>=1.24.0 <2.0.0'
+ sdk: '>=2.0.0-dev.30 <2.0.0'
dependencies:
path: ^1.4.0
diff --git a/packages/usage/test/hit_types_test.dart b/packages/usage/test/hit_types_test.dart
index e8bf241..a7ee490 100644
--- a/packages/usage/test/hit_types_test.dart
+++ b/packages/usage/test/hit_types_test.dart
@@ -11,7 +11,7 @@
import 'src/common.dart';
-main() => defineTests();
+void main() => defineTests();
void defineTests() {
group('screenView', () {
diff --git a/packages/usage/test/src/common.dart b/packages/usage/test/src/common.dart
index 7799394..c2f1a5e 100644
--- a/packages/usage/test/src/common.dart
+++ b/packages/usage/test/src/common.dart
@@ -12,9 +12,9 @@
AnalyticsImplMock createMock({Map<String, dynamic> props}) =>
new AnalyticsImplMock('UA-0', props: props);
-was(Map m, String type) => expect(m['t'], type);
-has(Map m, String key) => expect(m[key], isNotNull);
-hasnt(Map m, String key) => expect(m[key], isNull);
+void was(Map m, String type) => expect(m['t'], type);
+void has(Map m, String key) => expect(m[key], isNotNull);
+void hasnt(Map m, String key) => expect(m[key], isNull);
class AnalyticsImplMock extends AnalyticsImpl {
MockProperties get mockProperties => properties;
diff --git a/packages/usage/test/usage_impl_io_test.dart b/packages/usage/test/usage_impl_io_test.dart
index 8889077..a9c30d4 100644
--- a/packages/usage/test/usage_impl_io_test.dart
+++ b/packages/usage/test/usage_impl_io_test.dart
@@ -11,7 +11,7 @@
import 'package:test/test.dart';
import 'package:usage/src/usage_impl_io.dart';
-main() => defineTests();
+void main() => defineTests();
void defineTests() {
group('IOPostHandler', () {
@@ -93,7 +93,7 @@
MockHttpClientResponse(this.client);
@override
- Future/*<E>*/ drain/*<E>*/([/*=E*/ futureValue]) {
+ Future<E> drain<E>([E futureValue]) {
client.sendCount++;
return new Future.value();
}
diff --git a/packages/usage/test/usage_impl_test.dart b/packages/usage/test/usage_impl_test.dart
index a4ca3ac..2a3ea35 100644
--- a/packages/usage/test/usage_impl_test.dart
+++ b/packages/usage/test/usage_impl_test.dart
@@ -9,7 +9,7 @@
import 'src/common.dart';
-main() => defineTests();
+void main() => defineTests();
void defineTests() {
group('ThrottlingBucket', () {
diff --git a/packages/usage/test/usage_test.dart b/packages/usage/test/usage_test.dart
index 37500a9..d97d7b4 100644
--- a/packages/usage/test/usage_test.dart
+++ b/packages/usage/test/usage_test.dart
@@ -7,7 +7,7 @@
import 'package:test/test.dart';
import 'package:usage/usage.dart';
-main() => defineTests();
+void main() => defineTests();
void defineTests() {
group('AnalyticsMock', () {
diff --git a/packages/usage/test/uuid_test.dart b/packages/usage/test/uuid_test.dart
index 98dbab8..81762e7 100644
--- a/packages/usage/test/uuid_test.dart
+++ b/packages/usage/test/uuid_test.dart
@@ -7,7 +7,7 @@
import 'package:test/test.dart';
import 'package:usage/uuid/uuid.dart';
-main() => defineTests();
+void main() => defineTests();
void defineTests() {
group('uuid', () {
diff --git a/packages/usage/test/web_test.dart b/packages/usage/test/web_test.dart
index 8d91ae9..1162559 100644
--- a/packages/usage/test/web_test.dart
+++ b/packages/usage/test/web_test.dart
@@ -2,7 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-@TestOn("browser")
+@TestOn('browser')
library usage.web_test;
import 'dart:async';
diff --git a/pubspec.yaml b/pubspec.yaml
index 84c91cf..2042c4e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,10 +1,10 @@
name: observatory
dependencies:
- charted: any
- logging: any
stack_trace: any
unittest: any
usage: any
-
- # charted neglects to declare this dependency
- collection: any
+ # This will be charted 0.6.1 when pushed to pub.
+ charted:
+ git:
+ url: git://github.com/google/charted
+ ref: b847ace40c4193ce3df30c00032d16d00d1a59b7