// Copyright 2014 The Flutter Authors. 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:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('ClampingScrollSimulation has a stable initial conditions', () { void checkInitialConditions(double position, double velocity) { final ClampingScrollSimulation simulation = ClampingScrollSimulation(position: position, velocity: velocity); expect(simulation.x(0.0), moreOrLessEquals(position)); expect(simulation.dx(0.0), moreOrLessEquals(velocity)); } checkInitialConditions(51.0, 2866.91537); checkInitialConditions(584.0, 2617.294734); checkInitialConditions(345.0, 1982.785934); checkInitialConditions(0.0, 1831.366634); checkInitialConditions(-156.2, 1541.57665); checkInitialConditions(4.0, 1139.250439); checkInitialConditions(4534.0, 1073.553798); checkInitialConditions(75.0, 614.2093); checkInitialConditions(5469.0, 182.114534); }); test('ClampingScrollSimulation only decelerates, never speeds up', () { // Regression test for https://github.com/flutter/flutter/issues/113424 final ClampingScrollSimulation simulation = ClampingScrollSimulation(position: 0, velocity: 8000.0); double time = 0.0; double velocity = simulation.dx(time); while (!simulation.isDone(time)) { expect(time, lessThan(3.0)); time += 1 / 60; final double nextVelocity = simulation.dx(time); expect(nextVelocity, lessThanOrEqualTo(velocity)); velocity = nextVelocity; } }); test('ClampingScrollSimulation reaches a smooth stop: velocity is continuous and goes to zero', () { // Regression test for https://github.com/flutter/flutter/issues/113424 const double initialVelocity = 8000.0; const double maxDeceleration = 5130.0; // -acceleration(initialVelocity), from formula below final ClampingScrollSimulation simulation = ClampingScrollSimulation(position: 0, velocity: initialVelocity); double time = 0.0; double velocity = simulation.dx(time); const double delta = 1 / 60; do { expect(time, lessThan(3.0)); time += delta; final double nextVelocity = simulation.dx(time); expect((nextVelocity - velocity).abs(), lessThan(delta * maxDeceleration)); velocity = nextVelocity; } while (!simulation.isDone(time)); expect(velocity, moreOrLessEquals(0.0)); }); test('ClampingScrollSimulation is ballistic', () { // Regression test for https://github.com/flutter/flutter/issues/120338 const double delta = 1 / 90; final ClampingScrollSimulation undisturbed = ClampingScrollSimulation(position: 0, velocity: 8000.0); double time = 0.0; ClampingScrollSimulation restarted = undisturbed; final List<double> xsRestarted = <double>[]; final List<double> xsUndisturbed = <double>[]; final List<double> dxsRestarted = <double>[]; final List<double> dxsUndisturbed = <double>[]; do { expect(time, lessThan(4.0)); time += delta; restarted = ClampingScrollSimulation( position: restarted.x(delta), velocity: restarted.dx(delta)); xsRestarted.add(restarted.x(0)); xsUndisturbed.add(undisturbed.x(time)); dxsRestarted.add(restarted.dx(0)); dxsUndisturbed.add(undisturbed.dx(time)); } while (!restarted.isDone(0) || !undisturbed.isDone(time)); // Compare the headline number first: the total distances traveled. // This way, if the test fails, it shows the big final difference // instead of the tiny difference that's in the very first frame. expect(xsRestarted.last, moreOrLessEquals(xsUndisturbed.last)); // The whole trajectories along the way should match too. for (int i = 0; i < xsRestarted.length; i++) { expect(xsRestarted[i], moreOrLessEquals(xsUndisturbed[i])); expect(dxsRestarted[i], moreOrLessEquals(dxsUndisturbed[i])); } }); test('ClampingScrollSimulation satisfies a physical acceleration formula', () { // Different regression test for https://github.com/flutter/flutter/issues/120338 // // This one provides a formula for the particle's acceleration as a function // of its velocity, and checks that it behaves according to that formula. // The point isn't that it's this specific formula, but just that there's // some formula which depends only on velocity, not time, so that the // physical metaphor makes sense. // Copied from the implementation. final double kDecelerationRate = math.log(0.78) / math.log(0.9); // Same as the referenceVelocity in _flingDuration. const double referenceVelocity = .015 * 9.80665 * 39.37 * 160.0 * 0.84 / 0.35; // The value of _duration when velocity == referenceVelocity. final double referenceDuration = kDecelerationRate * 0.35; // The rate of deceleration when dx(time) == referenceVelocity. final double referenceDeceleration = (kDecelerationRate - 1) * referenceVelocity / referenceDuration; double acceleration(double velocity) { return - velocity.sign * referenceDeceleration * math.pow(velocity.abs() / referenceVelocity, (kDecelerationRate - 2) / (kDecelerationRate - 1)); } double jerk(double velocity) { return referenceVelocity / referenceDuration / referenceDuration * (kDecelerationRate - 1) * (kDecelerationRate - 2) * math.pow(velocity.abs() / referenceVelocity, (kDecelerationRate - 3) / (kDecelerationRate - 1)); } void checkAcceleration(double position, double velocity) { final ClampingScrollSimulation simulation = ClampingScrollSimulation(position: position, velocity: velocity); double time = 0.0; const double delta = 1/60; for (; time < 2.0; time += delta) { final double difference = simulation.dx(time + delta) - simulation.dx(time); final double predictedDifference = delta * acceleration(simulation.dx(time + delta/2)); final double maxThirdDerivative = jerk(simulation.dx(time + delta)); expect((difference - predictedDifference).abs(), lessThan(maxThirdDerivative * math.pow(delta, 2)/2)); } } checkAcceleration(51.0, 2866.91537); checkAcceleration(584.0, 2617.294734); checkAcceleration(345.0, 1982.785934); checkAcceleration(0.0, 1831.366634); checkAcceleration(-156.2, 1541.57665); checkAcceleration(4.0, 1139.250439); checkAcceleration(4534.0, 1073.553798); checkAcceleration(75.0, 614.2093); checkAcceleration(5469.0, 182.114534); }); }