@ -22,7 +22,7 @@ export class Animation {
|
||||
const errors: any[] = [];
|
||||
const ast = buildAnimationAst(_driver, input, errors);
|
||||
if (errors.length) {
|
||||
const errorMessage = `animation validation failed:\n${errors.join("\n")}`;
|
||||
const errorMessage = `animation validation failed:\n${errors.join('\n')}`;
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
this._animationAst = ast;
|
||||
@ -42,7 +42,7 @@ export class Animation {
|
||||
this._driver, element, this._animationAst, ENTER_CLASSNAME, LEAVE_CLASSNAME, start, dest,
|
||||
options, subInstructions, errors);
|
||||
if (errors.length) {
|
||||
const errorMessage = `animation building failed:\n${errors.join("\n")}`;
|
||||
const errorMessage = `animation building failed:\n${errors.join('\n')}`;
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
return result;
|
||||
|
@ -74,7 +74,9 @@ export interface StyleAst extends Ast<AnimationMetadataType.Style> {
|
||||
isEmptyStep?: boolean;
|
||||
}
|
||||
|
||||
export interface KeyframesAst extends Ast<AnimationMetadataType.Keyframes> { styles: StyleAst[]; }
|
||||
export interface KeyframesAst extends Ast<AnimationMetadataType.Keyframes> {
|
||||
styles: StyleAst[];
|
||||
}
|
||||
|
||||
export interface ReferenceAst extends Ast<AnimationMetadataType.Reference> {
|
||||
animation: Ast<AnimationMetadataType>;
|
||||
|
@ -5,11 +5,11 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AUTO_STYLE, AnimateTimings, AnimationAnimateChildMetadata, AnimationAnimateMetadata, AnimationAnimateRefMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationMetadataType, AnimationOptions, AnimationQueryMetadata, AnimationQueryOptions, AnimationReferenceMetadata, AnimationSequenceMetadata, AnimationStaggerMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata, AnimationTriggerMetadata, style, ɵStyleData} from '@angular/animations';
|
||||
import {AnimateTimings, AnimationAnimateChildMetadata, AnimationAnimateMetadata, AnimationAnimateRefMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationMetadataType, AnimationOptions, AnimationQueryMetadata, AnimationQueryOptions, AnimationReferenceMetadata, AnimationSequenceMetadata, AnimationStaggerMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata, AnimationTriggerMetadata, AUTO_STYLE, style, ɵStyleData} from '@angular/animations';
|
||||
|
||||
import {AnimationDriver} from '../render/animation_driver';
|
||||
import {getOrSetAsInMap} from '../render/shared';
|
||||
import {ENTER_SELECTOR, LEAVE_SELECTOR, NG_ANIMATING_SELECTOR, NG_TRIGGER_SELECTOR, SUBSTITUTION_EXPR_START, copyObj, extractStyleParams, iteratorToArray, normalizeAnimationEntry, resolveTiming, validateStyleParams, visitDslNode} from '../util';
|
||||
import {copyObj, ENTER_SELECTOR, extractStyleParams, iteratorToArray, LEAVE_SELECTOR, NG_ANIMATING_SELECTOR, NG_TRIGGER_SELECTOR, normalizeAnimationEntry, resolveTiming, SUBSTITUTION_EXPR_START, validateStyleParams, visitDslNode} from '../util';
|
||||
|
||||
import {AnimateAst, AnimateChildAst, AnimateRefAst, Ast, DynamicTimingAst, GroupAst, KeyframesAst, QueryAst, ReferenceAst, SequenceAst, StaggerAst, StateAst, StyleAst, TimingAst, TransitionAst, TriggerAst} from './animation_ast';
|
||||
import {AnimationDslVisitor} from './animation_dsl_visitor';
|
||||
@ -55,7 +55,7 @@ const SELF_TOKEN_REGEX = new RegExp(`\s*${SELF_TOKEN}\s*,?`, 'g');
|
||||
* Otherwise an error will be thrown.
|
||||
*/
|
||||
export function buildAnimationAst(
|
||||
driver: AnimationDriver, metadata: AnimationMetadata | AnimationMetadata[],
|
||||
driver: AnimationDriver, metadata: AnimationMetadata|AnimationMetadata[],
|
||||
errors: any[]): Ast<AnimationMetadataType> {
|
||||
return new AnimationAstBuilderVisitor(driver).build(metadata, errors);
|
||||
}
|
||||
@ -114,7 +114,11 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
|
||||
|
||||
return {
|
||||
type: AnimationMetadataType.Trigger,
|
||||
name: metadata.name, states, transitions, queryCount, depCount,
|
||||
name: metadata.name,
|
||||
states,
|
||||
transitions,
|
||||
queryCount,
|
||||
depCount,
|
||||
options: null
|
||||
};
|
||||
}
|
||||
@ -139,8 +143,10 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
|
||||
});
|
||||
if (missingSubs.size) {
|
||||
const missingSubsArr = iteratorToArray(missingSubs.values());
|
||||
context.errors.push(
|
||||
`state("${metadata.name}", ...) must define default values for all the following style substitutions: ${missingSubsArr.join(', ')}`);
|
||||
context.errors.push(`state("${
|
||||
metadata
|
||||
.name}", ...) must define default values for all the following style substitutions: ${
|
||||
missingSubsArr.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,7 +216,7 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
|
||||
let isEmpty = false;
|
||||
if (!styleMetadata) {
|
||||
isEmpty = true;
|
||||
const newStyleData: {[prop: string]: string | number} = {};
|
||||
const newStyleData: {[prop: string]: string|number} = {};
|
||||
if (timingAst.easing) {
|
||||
newStyleData['easing'] = timingAst.easing;
|
||||
}
|
||||
@ -239,9 +245,9 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
|
||||
|
||||
private _makeStyleAst(metadata: AnimationStyleMetadata, context: AnimationAstBuilderContext):
|
||||
StyleAst {
|
||||
const styles: (ɵStyleData | string)[] = [];
|
||||
const styles: (ɵStyleData|string)[] = [];
|
||||
if (Array.isArray(metadata.styles)) {
|
||||
(metadata.styles as(ɵStyleData | string)[]).forEach(styleTuple => {
|
||||
(metadata.styles as (ɵStyleData | string)[]).forEach(styleTuple => {
|
||||
if (typeof styleTuple == 'string') {
|
||||
if (styleTuple == AUTO_STYLE) {
|
||||
styles.push(styleTuple);
|
||||
@ -282,7 +288,8 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
|
||||
type: AnimationMetadataType.Style,
|
||||
styles,
|
||||
easing: collectedEasing,
|
||||
offset: metadata.offset, containsDynamicStyles,
|
||||
offset: metadata.offset,
|
||||
containsDynamicStyles,
|
||||
options: null
|
||||
};
|
||||
}
|
||||
@ -300,19 +307,22 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
|
||||
|
||||
Object.keys(tuple).forEach(prop => {
|
||||
if (!this._driver.validateStyleProperty(prop)) {
|
||||
context.errors.push(
|
||||
`The provided animation property "${prop}" is not a supported CSS property for animations`);
|
||||
context.errors.push(`The provided animation property "${
|
||||
prop}" is not a supported CSS property for animations`);
|
||||
return;
|
||||
}
|
||||
|
||||
const collectedStyles = context.collectedStyles[context.currentQuerySelector !];
|
||||
const collectedStyles = context.collectedStyles[context.currentQuerySelector!];
|
||||
const collectedEntry = collectedStyles[prop];
|
||||
let updateCollectedStyle = true;
|
||||
if (collectedEntry) {
|
||||
if (startTime != endTime && startTime >= collectedEntry.startTime &&
|
||||
endTime <= collectedEntry.endTime) {
|
||||
context.errors.push(
|
||||
`The CSS property "${prop}" that exists between the times of "${collectedEntry.startTime}ms" and "${collectedEntry.endTime}ms" is also being animated in a parallel animation between the times of "${startTime}ms" and "${endTime}ms"`);
|
||||
context.errors.push(`The CSS property "${prop}" that exists between the times of "${
|
||||
collectedEntry.startTime}ms" and "${
|
||||
collectedEntry
|
||||
.endTime}ms" is also being animated in a parallel animation between the times of "${
|
||||
startTime}ms" and "${endTime}ms"`);
|
||||
updateCollectedStyle = false;
|
||||
}
|
||||
|
||||
@ -383,7 +393,7 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
|
||||
|
||||
const limit = length - 1;
|
||||
const currentTime = context.currentTime;
|
||||
const currentAnimateTimings = context.currentAnimateTimings !;
|
||||
const currentAnimateTimings = context.currentAnimateTimings!;
|
||||
const animateDuration = currentAnimateTimings.duration;
|
||||
keyframes.forEach((kf, i) => {
|
||||
const offset = generatedOffset > 0 ? (i == limit ? 1 : (generatedOffset * i)) : offsets[i];
|
||||
@ -427,7 +437,7 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
|
||||
}
|
||||
|
||||
visitQuery(metadata: AnimationQueryMetadata, context: AnimationAstBuilderContext): QueryAst {
|
||||
const parentSelector = context.currentQuerySelector !;
|
||||
const parentSelector = context.currentQuerySelector!;
|
||||
const options = (metadata.options || {}) as AnimationQueryOptions;
|
||||
|
||||
context.queryCount++;
|
||||
@ -445,7 +455,9 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
|
||||
type: AnimationMetadataType.Query,
|
||||
selector,
|
||||
limit: options.limit || 0,
|
||||
optional: !!options.optional, includeSelf, animation,
|
||||
optional: !!options.optional,
|
||||
includeSelf,
|
||||
animation,
|
||||
originalSelector: metadata.selector,
|
||||
options: normalizeAnimationOptions(metadata.options)
|
||||
};
|
||||
@ -462,7 +474,8 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
|
||||
|
||||
return {
|
||||
type: AnimationMetadataType.Stagger,
|
||||
animation: visitDslNode(this, normalizeAnimationEntry(metadata.animation), context), timings,
|
||||
animation: visitDslNode(this, normalizeAnimationEntry(metadata.animation), context),
|
||||
timings,
|
||||
options: null
|
||||
};
|
||||
}
|
||||
@ -483,7 +496,7 @@ function normalizeSelector(selector: string): [string, boolean] {
|
||||
}
|
||||
|
||||
|
||||
function normalizeParams(obj: {[key: string]: any} | any): {[key: string]: any}|null {
|
||||
function normalizeParams(obj: {[key: string]: any}|any): {[key: string]: any}|null {
|
||||
return obj ? copyObj(obj) : null;
|
||||
}
|
||||
|
||||
@ -504,7 +517,7 @@ export class AnimationAstBuilderContext {
|
||||
constructor(public errors: any[]) {}
|
||||
}
|
||||
|
||||
function consumeOffset(styles: ɵStyleData | string | (ɵStyleData | string)[]): number|null {
|
||||
function consumeOffset(styles: ɵStyleData|string|(ɵStyleData | string)[]): number|null {
|
||||
if (typeof styles == 'string') return null;
|
||||
|
||||
let offset: number|null = null;
|
||||
@ -529,7 +542,7 @@ function isObject(value: any): boolean {
|
||||
return !Array.isArray(value) && typeof value == 'object';
|
||||
}
|
||||
|
||||
function constructTimingAst(value: string | number | AnimateTimings, errors: any[]) {
|
||||
function constructTimingAst(value: string|number|AnimateTimings, errors: any[]) {
|
||||
let timings: AnimateTimings|null = null;
|
||||
if (value.hasOwnProperty('duration')) {
|
||||
timings = value as AnimateTimings;
|
||||
@ -551,11 +564,11 @@ function constructTimingAst(value: string | number | AnimateTimings, errors: any
|
||||
return makeTimingAst(timings.duration, timings.delay, timings.easing);
|
||||
}
|
||||
|
||||
function normalizeAnimationOptions(options: AnimationOptions | null): AnimationOptions {
|
||||
function normalizeAnimationOptions(options: AnimationOptions|null): AnimationOptions {
|
||||
if (options) {
|
||||
options = copyObj(options);
|
||||
if (options['params']) {
|
||||
options['params'] = normalizeParams(options['params']) !;
|
||||
options['params'] = normalizeParams(options['params'])!;
|
||||
}
|
||||
} else {
|
||||
options = {};
|
||||
@ -563,6 +576,6 @@ function normalizeAnimationOptions(options: AnimationOptions | null): AnimationO
|
||||
return options;
|
||||
}
|
||||
|
||||
function makeTimingAst(duration: number, delay: number, easing: string | null): TimingAst {
|
||||
function makeTimingAst(duration: number, delay: number, easing: string|null): TimingAst {
|
||||
return {duration, delay, easing};
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AUTO_STYLE, AnimateChildOptions, AnimateTimings, AnimationMetadataType, AnimationOptions, AnimationQueryOptions, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations';
|
||||
import {AnimateChildOptions, AnimateTimings, AnimationMetadataType, AnimationOptions, AnimationQueryOptions, AUTO_STYLE, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations';
|
||||
|
||||
import {AnimationDriver} from '../render/animation_driver';
|
||||
import {copyObj, copyStyles, interpolateParams, iteratorToArray, resolveTiming, resolveTimingValue, visitDslNode} from '../util';
|
||||
@ -301,7 +301,7 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
|
||||
|
||||
visitStyle(ast: StyleAst, context: AnimationTimelineContext) {
|
||||
const timeline = context.currentTimeline;
|
||||
const timings = context.currentAnimateTimings !;
|
||||
const timings = context.currentAnimateTimings!;
|
||||
|
||||
// this is a special case for when a style() call
|
||||
// directly follows an animate() call (but not inside of an animate() call)
|
||||
@ -320,8 +320,8 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
|
||||
}
|
||||
|
||||
visitKeyframes(ast: KeyframesAst, context: AnimationTimelineContext) {
|
||||
const currentAnimateTimings = context.currentAnimateTimings !;
|
||||
const startTime = (context.currentTimeline !).duration;
|
||||
const currentAnimateTimings = context.currentAnimateTimings!;
|
||||
const startTime = (context.currentTimeline!).duration;
|
||||
const duration = currentAnimateTimings.duration;
|
||||
const innerContext = context.createSubContext();
|
||||
const innerTimeline = innerContext.currentTimeline;
|
||||
@ -351,8 +351,9 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
|
||||
const options = (ast.options || {}) as AnimationQueryOptions;
|
||||
const delay = options.delay ? resolveTimingValue(options.delay) : 0;
|
||||
|
||||
if (delay && (context.previousNode.type === AnimationMetadataType.Style ||
|
||||
(startTime == 0 && context.currentTimeline.getCurrentStyleProperties().length))) {
|
||||
if (delay &&
|
||||
(context.previousNode.type === AnimationMetadataType.Style ||
|
||||
(startTime == 0 && context.currentTimeline.getCurrentStyleProperties().length))) {
|
||||
context.currentTimeline.snapshotCurrentStyles();
|
||||
context.previousNode = DEFAULT_NOOP_PREVIOUS_NODE;
|
||||
}
|
||||
@ -365,7 +366,6 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
|
||||
context.currentQueryTotal = elms.length;
|
||||
let sameElementTimeline: TimelineBuilder|null = null;
|
||||
elms.forEach((element, i) => {
|
||||
|
||||
context.currentQueryIndex = i;
|
||||
const innerContext = context.createSubContext(ast.options, element);
|
||||
if (delay) {
|
||||
@ -400,7 +400,7 @@ export class AnimationTimelineBuilderVisitor implements AstVisitor {
|
||||
}
|
||||
|
||||
visitStagger(ast: StaggerAst, context: AnimationTimelineContext) {
|
||||
const parentContext = context.parentContext !;
|
||||
const parentContext = context.parentContext!;
|
||||
const tl = context.currentTimeline;
|
||||
const timings = ast.timings;
|
||||
const duration = Math.abs(timings.duration);
|
||||
@ -460,7 +460,9 @@ export class AnimationTimelineContext {
|
||||
timelines.push(this.currentTimeline);
|
||||
}
|
||||
|
||||
get params() { return this.options.params; }
|
||||
get params() {
|
||||
return this.options.params;
|
||||
}
|
||||
|
||||
updateOptions(options: AnimationOptions|null, skipIfExists?: boolean) {
|
||||
if (!options) return;
|
||||
@ -479,7 +481,7 @@ export class AnimationTimelineContext {
|
||||
|
||||
const newParams = newOptions.params;
|
||||
if (newParams) {
|
||||
let paramsToUpdate: {[name: string]: any} = optionsToUpdate.params !;
|
||||
let paramsToUpdate: {[name: string]: any} = optionsToUpdate.params!;
|
||||
if (!paramsToUpdate) {
|
||||
paramsToUpdate = this.options.params = {};
|
||||
}
|
||||
@ -498,7 +500,9 @@ export class AnimationTimelineContext {
|
||||
const oldParams = this.options.params;
|
||||
if (oldParams) {
|
||||
const params: {[name: string]: any} = options['params'] = {};
|
||||
Object.keys(oldParams).forEach(name => { params[name] = oldParams[name]; });
|
||||
Object.keys(oldParams).forEach(name => {
|
||||
params[name] = oldParams[name];
|
||||
});
|
||||
}
|
||||
}
|
||||
return options;
|
||||
@ -576,8 +580,8 @@ export class AnimationTimelineContext {
|
||||
}
|
||||
|
||||
if (!optional && results.length == 0) {
|
||||
errors.push(
|
||||
`\`query("${originalSelector}")\` returned zero elements. (Use \`query("${originalSelector}", { optional: true })\` if you wish to allow this.)`);
|
||||
errors.push(`\`query("${originalSelector}")\` returned zero elements. (Use \`query("${
|
||||
originalSelector}", { optional: true })\` if you wish to allow this.)`);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
@ -587,7 +591,7 @@ export class AnimationTimelineContext {
|
||||
export class TimelineBuilder {
|
||||
public duration: number = 0;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
public easing !: string | null;
|
||||
public easing!: string|null;
|
||||
private _previousKeyframe: ɵStyleData = {};
|
||||
private _currentKeyframe: ɵStyleData = {};
|
||||
private _keyframes = new Map<number, ɵStyleData>();
|
||||
@ -606,7 +610,7 @@ export class TimelineBuilder {
|
||||
}
|
||||
|
||||
this._localTimelineStyles = Object.create(this._backFill, {});
|
||||
this._globalTimelineStyles = this._elementTimelineStylesLookup.get(element) !;
|
||||
this._globalTimelineStyles = this._elementTimelineStylesLookup.get(element)!;
|
||||
if (!this._globalTimelineStyles) {
|
||||
this._globalTimelineStyles = this._localTimelineStyles;
|
||||
this._elementTimelineStylesLookup.set(element, this._localTimelineStyles);
|
||||
@ -625,9 +629,13 @@ export class TimelineBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentStyleProperties(): string[] { return Object.keys(this._currentKeyframe); }
|
||||
getCurrentStyleProperties(): string[] {
|
||||
return Object.keys(this._currentKeyframe);
|
||||
}
|
||||
|
||||
get currentTime() { return this.startTime + this.duration; }
|
||||
get currentTime() {
|
||||
return this.startTime + this.duration;
|
||||
}
|
||||
|
||||
delayNextStep(delay: number) {
|
||||
// in the event that a style() step is placed right before a stagger()
|
||||
@ -656,7 +664,7 @@ export class TimelineBuilder {
|
||||
if (this._currentKeyframe) {
|
||||
this._previousKeyframe = this._currentKeyframe;
|
||||
}
|
||||
this._currentKeyframe = this._keyframes.get(this.duration) !;
|
||||
this._currentKeyframe = this._keyframes.get(this.duration)!;
|
||||
if (!this._currentKeyframe) {
|
||||
this._currentKeyframe = Object.create(this._backFill, {});
|
||||
this._keyframes.set(this.duration, this._currentKeyframe);
|
||||
@ -680,7 +688,9 @@ export class TimelineBuilder {
|
||||
this._styleSummary[prop] = {time: this.currentTime, value};
|
||||
}
|
||||
|
||||
allowOnlyTimelineStyles() { return this._currentEmptyStepKeyframe !== this._currentKeyframe; }
|
||||
allowOnlyTimelineStyles() {
|
||||
return this._currentEmptyStepKeyframe !== this._currentKeyframe;
|
||||
}
|
||||
|
||||
applyEmptyStep(easing: string|null) {
|
||||
if (easing) {
|
||||
@ -748,7 +758,9 @@ export class TimelineBuilder {
|
||||
});
|
||||
}
|
||||
|
||||
getFinalKeyframe() { return this._keyframes.get(this.duration); }
|
||||
getFinalKeyframe() {
|
||||
return this._keyframes.get(this.duration);
|
||||
}
|
||||
|
||||
get properties() {
|
||||
const properties: string[] = [];
|
||||
@ -820,7 +832,9 @@ class SubTimelineBuilder extends TimelineBuilder {
|
||||
this.timings = {duration: timings.duration, delay: timings.delay, easing: timings.easing};
|
||||
}
|
||||
|
||||
containsAnimation(): boolean { return this.keyframes.length > 1; }
|
||||
containsAnimation(): boolean {
|
||||
return this.keyframes.length > 1;
|
||||
}
|
||||
|
||||
buildKeyframes(): AnimationTimelineInstruction {
|
||||
let keyframes = this.keyframes;
|
||||
@ -883,13 +897,15 @@ function roundOffset(offset: number, decimalPoints = 3): number {
|
||||
return Math.round(offset * mult) / mult;
|
||||
}
|
||||
|
||||
function flattenStyles(input: (ɵStyleData | string)[], allStyles: ɵStyleData) {
|
||||
function flattenStyles(input: (ɵStyleData|string)[], allStyles: ɵStyleData) {
|
||||
const styles: ɵStyleData = {};
|
||||
let allProperties: string[];
|
||||
input.forEach(token => {
|
||||
if (token === '*') {
|
||||
allProperties = allProperties || Object.keys(allStyles);
|
||||
allProperties.forEach(prop => { styles[prop] = AUTO_STYLE; });
|
||||
allProperties.forEach(prop => {
|
||||
styles[prop] = AUTO_STYLE;
|
||||
});
|
||||
} else {
|
||||
copyStyles(token as ɵStyleData, false, styles);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ export interface AnimationTimelineInstruction extends AnimationEngineInstruction
|
||||
|
||||
export function createTimelineInstruction(
|
||||
element: any, keyframes: ɵStyleData[], preStyleProps: string[], postStyleProps: string[],
|
||||
duration: number, delay: number, easing: string | null = null,
|
||||
duration: number, delay: number, easing: string|null = null,
|
||||
subTimeline: boolean = false): AnimationTimelineInstruction {
|
||||
return {
|
||||
type: AnimationTransitionInstructionType.TimelineAnimation,
|
||||
@ -33,6 +33,8 @@ export function createTimelineInstruction(
|
||||
postStyleProps,
|
||||
duration,
|
||||
delay,
|
||||
totalTime: duration + delay, easing, subTimeline
|
||||
totalTime: duration + delay,
|
||||
easing,
|
||||
subTimeline
|
||||
};
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ export declare type TransitionMatcherFn =
|
||||
(fromState: any, toState: any, element: any, params: {[key: string]: any}) => boolean;
|
||||
|
||||
export function parseTransitionExpr(
|
||||
transitionValue: string | TransitionMatcherFn, errors: string[]): TransitionMatcherFn[] {
|
||||
transitionValue: string|TransitionMatcherFn, errors: string[]): TransitionMatcherFn[] {
|
||||
const expressions: TransitionMatcherFn[] = [];
|
||||
if (typeof transitionValue == 'string') {
|
||||
transitionValue.split(/\s*,\s*/).forEach(
|
||||
|
@ -55,13 +55,16 @@ export class AnimationTransitionFactory {
|
||||
|
||||
const animationOptions = {params: {...transitionAnimationParams, ...nextAnimationParams}};
|
||||
|
||||
const timelines = skipAstBuild ? [] : buildAnimationTimelines(
|
||||
driver, element, this.ast.animation, enterClassName,
|
||||
leaveClassName, currentStateStyles, nextStateStyles,
|
||||
animationOptions, subInstructions, errors);
|
||||
const timelines = skipAstBuild ?
|
||||
[] :
|
||||
buildAnimationTimelines(
|
||||
driver, element, this.ast.animation, enterClassName, leaveClassName, currentStateStyles,
|
||||
nextStateStyles, animationOptions, subInstructions, errors);
|
||||
|
||||
let totalTime = 0;
|
||||
timelines.forEach(tl => { totalTime = Math.max(tl.duration + tl.delay, totalTime); });
|
||||
timelines.forEach(tl => {
|
||||
totalTime = Math.max(tl.duration + tl.delay, totalTime);
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
return createTransitionInstruction(
|
||||
|
@ -22,8 +22,8 @@ export function buildTrigger(name: string, ast: TriggerAst): AnimationTrigger {
|
||||
}
|
||||
|
||||
/**
|
||||
* @publicApi
|
||||
*/
|
||||
* @publicApi
|
||||
*/
|
||||
export class AnimationTrigger {
|
||||
public transitionFactories: AnimationTransitionFactory[] = [];
|
||||
public fallbackTransition: AnimationTransitionFactory;
|
||||
@ -45,7 +45,9 @@ export class AnimationTrigger {
|
||||
this.fallbackTransition = createFallbackTransition(name, this.states);
|
||||
}
|
||||
|
||||
get containsQueries() { return this.ast.queryCount > 0; }
|
||||
get containsQueries() {
|
||||
return this.ast.queryCount > 0;
|
||||
}
|
||||
|
||||
matchTransition(currentState: any, nextState: any, element: any, params: {[key: string]: any}):
|
||||
AnimationTransitionFactory|null {
|
||||
|
@ -28,7 +28,11 @@ export class ElementInstructionMap {
|
||||
existingInstructions.push(...instructions);
|
||||
}
|
||||
|
||||
has(element: any): boolean { return this._map.has(element); }
|
||||
has(element: any): boolean {
|
||||
return this._map.has(element);
|
||||
}
|
||||
|
||||
clear() { this._map.clear(); }
|
||||
clear() {
|
||||
this._map.clear();
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,9 @@ export abstract class AnimationStyleNormalizer {
|
||||
* @publicApi
|
||||
*/
|
||||
export class NoopAnimationStyleNormalizer {
|
||||
normalizePropertyName(propertyName: string, errors: string[]): string { return propertyName; }
|
||||
normalizePropertyName(propertyName: string, errors: string[]): string {
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
normalizeStyleValue(
|
||||
userProvidedProperty: string, normalizedProperty: string, value: string|number,
|
||||
|
@ -13,6 +13,6 @@ export {AnimationEngine as ɵAnimationEngine} from './render/animation_engine_ne
|
||||
export {CssKeyframesDriver as ɵCssKeyframesDriver} from './render/css_keyframes/css_keyframes_driver';
|
||||
export {CssKeyframesPlayer as ɵCssKeyframesPlayer} from './render/css_keyframes/css_keyframes_player';
|
||||
export {containsElement as ɵcontainsElement, invokeQuery as ɵinvokeQuery, matchesElement as ɵmatchesElement, validateStyleProperty as ɵvalidateStyleProperty} from './render/shared';
|
||||
export {WebAnimationsDriver as ɵWebAnimationsDriver, supportsWebAnimations as ɵsupportsWebAnimations} from './render/web_animations/web_animations_driver';
|
||||
export {supportsWebAnimations as ɵsupportsWebAnimations, WebAnimationsDriver as ɵWebAnimationsDriver} from './render/web_animations/web_animations_driver';
|
||||
export {WebAnimationsPlayer as ɵWebAnimationsPlayer} from './render/web_animations/web_animations_player';
|
||||
export {allowPreviousPlayerStylesMerge as ɵallowPreviousPlayerStylesMerge} from './util';
|
||||
|
@ -15,13 +15,17 @@ import {containsElement, invokeQuery, matchesElement, validateStyleProperty} fro
|
||||
*/
|
||||
@Injectable()
|
||||
export class NoopAnimationDriver implements AnimationDriver {
|
||||
validateStyleProperty(prop: string): boolean { return validateStyleProperty(prop); }
|
||||
validateStyleProperty(prop: string): boolean {
|
||||
return validateStyleProperty(prop);
|
||||
}
|
||||
|
||||
matchesElement(element: any, selector: string): boolean {
|
||||
return matchesElement(element, selector);
|
||||
}
|
||||
|
||||
containsElement(elm1: any, elm2: any): boolean { return containsElement(elm1, elm2); }
|
||||
containsElement(elm1: any, elm2: any): boolean {
|
||||
return containsElement(elm1, elm2);
|
||||
}
|
||||
|
||||
query(element: any, selector: string, multi: boolean): any[] {
|
||||
return invokeQuery(element, selector, multi);
|
||||
@ -32,7 +36,7 @@ export class NoopAnimationDriver implements AnimationDriver {
|
||||
}
|
||||
|
||||
animate(
|
||||
element: any, keyframes: {[key: string]: string | number}[], duration: number, delay: number,
|
||||
element: any, keyframes: {[key: string]: string|number}[], duration: number, delay: number,
|
||||
easing: string, previousPlayers: any[] = [],
|
||||
scrubberAccessRequested?: boolean): AnimationPlayer {
|
||||
return new NoopAnimationPlayer(duration, delay);
|
||||
@ -56,6 +60,6 @@ export abstract class AnimationDriver {
|
||||
abstract computeStyle(element: any, prop: string, defaultValue?: string): string;
|
||||
|
||||
abstract animate(
|
||||
element: any, keyframes: {[key: string]: string | number}[], duration: number, delay: number,
|
||||
element: any, keyframes: {[key: string]: string|number}[], duration: number, delay: number,
|
||||
easing?: string|null, previousPlayers?: any[], scrubberAccessRequested?: boolean): any;
|
||||
}
|
||||
|
@ -5,6 +5,11 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
export const enum AnimationTransitionInstructionType {TransitionAnimation, TimelineAnimation}
|
||||
export const enum AnimationTransitionInstructionType {
|
||||
TransitionAnimation,
|
||||
TimelineAnimation
|
||||
}
|
||||
|
||||
export interface AnimationEngineInstruction { type: AnimationTransitionInstructionType; }
|
||||
export interface AnimationEngineInstruction {
|
||||
type: AnimationTransitionInstructionType;
|
||||
}
|
||||
|
@ -45,8 +45,8 @@ export class AnimationEngine {
|
||||
const ast =
|
||||
buildAnimationAst(this._driver, metadata as AnimationMetadata, errors) as TriggerAst;
|
||||
if (errors.length) {
|
||||
throw new Error(
|
||||
`The animation trigger "${name}" has failed to build due to the following errors:\n - ${errors.join("\n - ")}`);
|
||||
throw new Error(`The animation trigger "${
|
||||
name}" has failed to build due to the following errors:\n - ${errors.join('\n - ')}`);
|
||||
}
|
||||
trigger = buildTrigger(name, ast);
|
||||
this._triggerCache[cacheKey] = trigger;
|
||||
@ -95,12 +95,16 @@ export class AnimationEngine {
|
||||
return this._transitionEngine.listen(namespaceId, element, eventName, eventPhase, callback);
|
||||
}
|
||||
|
||||
flush(microtaskId: number = -1): void { this._transitionEngine.flush(microtaskId); }
|
||||
flush(microtaskId: number = -1): void {
|
||||
this._transitionEngine.flush(microtaskId);
|
||||
}
|
||||
|
||||
get players(): AnimationPlayer[] {
|
||||
return (this._transitionEngine.players as AnimationPlayer[])
|
||||
.concat(this._timelineEngine.players as AnimationPlayer[]);
|
||||
}
|
||||
|
||||
whenRenderingDone(): Promise<any> { return this._transitionEngine.whenRenderingDone(); }
|
||||
whenRenderingDone(): Promise<any> {
|
||||
return this._transitionEngine.whenRenderingDone();
|
||||
}
|
||||
}
|
||||
|
@ -23,13 +23,17 @@ export class CssKeyframesDriver implements AnimationDriver {
|
||||
private readonly _head: any = document.querySelector('head');
|
||||
private _warningIssued = false;
|
||||
|
||||
validateStyleProperty(prop: string): boolean { return validateStyleProperty(prop); }
|
||||
validateStyleProperty(prop: string): boolean {
|
||||
return validateStyleProperty(prop);
|
||||
}
|
||||
|
||||
matchesElement(element: any, selector: string): boolean {
|
||||
return matchesElement(element, selector);
|
||||
}
|
||||
|
||||
containsElement(elm1: any, elm2: any): boolean { return containsElement(elm1, elm2); }
|
||||
containsElement(elm1: any, elm2: any): boolean {
|
||||
return containsElement(elm1, elm2);
|
||||
}
|
||||
|
||||
query(element: any, selector: string, multi: boolean): any[] {
|
||||
return invokeQuery(element, selector, multi);
|
||||
@ -104,7 +108,7 @@ export class CssKeyframesDriver implements AnimationDriver {
|
||||
|
||||
const animationName = `${KEYFRAMES_NAME_PREFIX}${this._count++}`;
|
||||
const kfElm = this.buildKeyframeElement(element, animationName, keyframes);
|
||||
document.querySelector('head') !.appendChild(kfElm);
|
||||
document.querySelector('head')!.appendChild(kfElm);
|
||||
|
||||
const specialStyles = packageNonAnimatableStyles(element, keyframes);
|
||||
const player = new CssKeyframesPlayer(
|
||||
@ -124,8 +128,8 @@ export class CssKeyframesDriver implements AnimationDriver {
|
||||
}
|
||||
}
|
||||
|
||||
function flattenKeyframesIntoStyles(
|
||||
keyframes: null | {[key: string]: any} | {[key: string]: any}[]): {[key: string]: any} {
|
||||
function flattenKeyframesIntoStyles(keyframes: null|{[key: string]: any}|
|
||||
{[key: string]: any}[]): {[key: string]: any} {
|
||||
let flatKeyframes: {[key: string]: any} = {};
|
||||
if (keyframes) {
|
||||
const kfs = Array.isArray(keyframes) ? keyframes : [keyframes];
|
||||
|
@ -14,7 +14,12 @@ import {ElementAnimationStyleHandler} from './element_animation_style_handler';
|
||||
const DEFAULT_FILL_MODE = 'forwards';
|
||||
const DEFAULT_EASING = 'linear';
|
||||
|
||||
export const enum AnimatorControlState {INITIALIZED = 1, STARTED = 2, FINISHED = 3, DESTROYED = 4}
|
||||
export const enum AnimatorControlState {
|
||||
INITIALIZED = 1,
|
||||
STARTED = 2,
|
||||
FINISHED = 3,
|
||||
DESTROYED = 4
|
||||
}
|
||||
|
||||
export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
private _onDoneFns: Function[] = [];
|
||||
@ -23,10 +28,10 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
|
||||
private _started = false;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
private _styler !: ElementAnimationStyleHandler;
|
||||
private _styler!: ElementAnimationStyleHandler;
|
||||
|
||||
// TODO(issue/24571): remove '!'.
|
||||
public parentPlayer !: AnimationPlayer;
|
||||
public parentPlayer!: AnimationPlayer;
|
||||
public readonly totalTime: number;
|
||||
public readonly easing: string;
|
||||
public currentSnapshot: {[key: string]: string} = {};
|
||||
@ -34,7 +39,7 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
private _state: AnimatorControlState = 0;
|
||||
|
||||
constructor(
|
||||
public readonly element: any, public readonly keyframes: {[key: string]: string | number}[],
|
||||
public readonly element: any, public readonly keyframes: {[key: string]: string|number}[],
|
||||
public readonly animationName: string, private readonly _duration: number,
|
||||
private readonly _delay: number, easing: string,
|
||||
private readonly _finalStyles: {[key: string]: any},
|
||||
@ -44,11 +49,17 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
this._buildStyler();
|
||||
}
|
||||
|
||||
onStart(fn: () => void): void { this._onStartFns.push(fn); }
|
||||
onStart(fn: () => void): void {
|
||||
this._onStartFns.push(fn);
|
||||
}
|
||||
|
||||
onDone(fn: () => void): void { this._onDoneFns.push(fn); }
|
||||
onDone(fn: () => void): void {
|
||||
this._onDoneFns.push(fn);
|
||||
}
|
||||
|
||||
onDestroy(fn: () => void): void { this._onDestroyFns.push(fn); }
|
||||
onDestroy(fn: () => void): void {
|
||||
this._onDestroyFns.push(fn);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.init();
|
||||
@ -86,11 +97,17 @@ export class CssKeyframesPlayer implements AnimationPlayer {
|
||||
this._flushDoneFns();
|
||||
}
|
||||
|
||||
setPosition(value: number) { this._styler.setPosition(value); }
|
||||
setPosition(value: number) {
|
||||
this._styler.setPosition(value);
|
||||
}
|
||||
|
||||
getPosition(): number { return this._styler.getPosition(); }
|
||||
getPosition(): number {
|
||||
return this._styler.getPosition();
|
||||
}
|
||||
|
||||
hasStarted(): boolean { return this._state >= AnimatorControlState.STARTED; }
|
||||
hasStarted(): boolean {
|
||||
return this._state >= AnimatorControlState.STARTED;
|
||||
}
|
||||
init(): void {
|
||||
if (this._state >= AnimatorControlState.INITIALIZED) return;
|
||||
this._state = AnimatorControlState.INITIALIZED;
|
||||
|
@ -22,7 +22,7 @@ export class DirectStylePlayer extends NoopAnimationPlayer {
|
||||
if (this.__initialized || !this._startingStyles) return;
|
||||
this.__initialized = true;
|
||||
Object.keys(this._styles).forEach(prop => {
|
||||
this._startingStyles ![prop] = this.element.style[prop];
|
||||
this._startingStyles![prop] = this.element.style[prop];
|
||||
});
|
||||
super.init();
|
||||
}
|
||||
@ -38,7 +38,7 @@ export class DirectStylePlayer extends NoopAnimationPlayer {
|
||||
destroy() {
|
||||
if (!this._startingStyles) return;
|
||||
Object.keys(this._startingStyles).forEach(prop => {
|
||||
const value = this._startingStyles ![prop];
|
||||
const value = this._startingStyles![prop];
|
||||
if (value) {
|
||||
this.element.style.setProperty(prop, value);
|
||||
} else {
|
||||
|
@ -28,14 +28,19 @@ export class ElementAnimationStyleHandler {
|
||||
apply() {
|
||||
applyKeyframeAnimation(
|
||||
this._element,
|
||||
`${this._duration}ms ${this._easing} ${this._delay}ms 1 normal ${this._fillMode} ${this._name}`);
|
||||
`${this._duration}ms ${this._easing} ${this._delay}ms 1 normal ${this._fillMode} ${
|
||||
this._name}`);
|
||||
addRemoveAnimationEvent(this._element, this._eventFn, false);
|
||||
this._startTime = Date.now();
|
||||
}
|
||||
|
||||
pause() { playPauseAnimation(this._element, this._name, 'paused'); }
|
||||
pause() {
|
||||
playPauseAnimation(this._element, this._name, 'paused');
|
||||
}
|
||||
|
||||
resume() { playPauseAnimation(this._element, this._name, 'running'); }
|
||||
resume() {
|
||||
playPauseAnimation(this._element, this._name, 'running');
|
||||
}
|
||||
|
||||
setPosition(position: number) {
|
||||
const index = findIndexForAnimation(this._element, this._name);
|
||||
@ -43,7 +48,9 @@ export class ElementAnimationStyleHandler {
|
||||
setAnimationStyle(this._element, 'Delay', `-${this._position}ms`, index);
|
||||
}
|
||||
|
||||
getPosition() { return this._position; }
|
||||
getPosition() {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
private _handleCallback(event: any) {
|
||||
const timestamp = event._ngTestManualTimestamp || Date.now();
|
||||
@ -70,7 +77,7 @@ export class ElementAnimationStyleHandler {
|
||||
}
|
||||
}
|
||||
|
||||
function playPauseAnimation(element: any, name: string, status: 'running' | 'paused') {
|
||||
function playPauseAnimation(element: any, name: string, status: 'running'|'paused') {
|
||||
const index = findIndexForAnimation(element, name);
|
||||
setAnimationStyle(element, 'PlayState', status, index);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AUTO_STYLE, AnimationEvent, AnimationPlayer, NoopAnimationPlayer, ɵAnimationGroupPlayer, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations';
|
||||
import {AnimationEvent, AnimationPlayer, AUTO_STYLE, NoopAnimationPlayer, ɵAnimationGroupPlayer, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations';
|
||||
|
||||
import {AnimationStyleNormalizer} from '../../src/dsl/style_normalization/animation_style_normalizer';
|
||||
import {AnimationDriver} from '../../src/render/animation_driver';
|
||||
@ -89,7 +89,7 @@ export function normalizeKeyframes(
|
||||
}
|
||||
|
||||
export function listenOnPlayer(
|
||||
player: AnimationPlayer, eventName: string, event: AnimationEvent | undefined,
|
||||
player: AnimationPlayer, eventName: string, event: AnimationEvent|undefined,
|
||||
callback: (event: any) => any) {
|
||||
switch (eventName) {
|
||||
case 'start':
|
||||
@ -125,7 +125,7 @@ export function makeAnimationEvent(
|
||||
}
|
||||
|
||||
export function getOrSetAsInMap(
|
||||
map: Map<any, any>| {[key: string]: any}, key: any, defaultValue: any) {
|
||||
map: Map<any, any>|{[key: string]: any}, key: any, defaultValue: any) {
|
||||
let value: any;
|
||||
if (map instanceof Map) {
|
||||
value = map.get(key);
|
||||
@ -161,7 +161,9 @@ let _query: (element: any, selector: string, multi: boolean) => any[] =
|
||||
const _isNode = isNode();
|
||||
if (_isNode || typeof Element !== 'undefined') {
|
||||
// this is well supported in all browsers
|
||||
_contains = (elm1: any, elm2: any) => { return elm1.contains(elm2) as boolean; };
|
||||
_contains = (elm1: any, elm2: any) => {
|
||||
return elm1.contains(elm2) as boolean;
|
||||
};
|
||||
|
||||
_matches = (() => {
|
||||
if (_isNode || Element.prototype.matches) {
|
||||
@ -203,15 +205,15 @@ let _IS_WEBKIT = false;
|
||||
export function validateStyleProperty(prop: string): boolean {
|
||||
if (!_CACHED_BODY) {
|
||||
_CACHED_BODY = getBodyNode() || {};
|
||||
_IS_WEBKIT = _CACHED_BODY !.style ? ('WebkitAppearance' in _CACHED_BODY !.style) : false;
|
||||
_IS_WEBKIT = _CACHED_BODY!.style ? ('WebkitAppearance' in _CACHED_BODY!.style) : false;
|
||||
}
|
||||
|
||||
let result = true;
|
||||
if (_CACHED_BODY !.style && !containsVendorPrefix(prop)) {
|
||||
result = prop in _CACHED_BODY !.style;
|
||||
if (_CACHED_BODY!.style && !containsVendorPrefix(prop)) {
|
||||
result = prop in _CACHED_BODY!.style;
|
||||
if (!result && _IS_WEBKIT) {
|
||||
const camelProp = 'Webkit' + prop.charAt(0).toUpperCase() + prop.substr(1);
|
||||
result = camelProp in _CACHED_BODY !.style;
|
||||
result = camelProp in _CACHED_BODY!.style;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ import {eraseStyles, setStyles} from '../util';
|
||||
* @returns an instance of `SpecialCasedStyles` if any special styles are detected otherwise `null`
|
||||
*/
|
||||
export function packageNonAnimatableStyles(
|
||||
element: any, styles: {[key: string]: any} | {[key: string]: any}[]): SpecialCasedStyles|null {
|
||||
element: any, styles: {[key: string]: any}|{[key: string]: any}[]): SpecialCasedStyles|null {
|
||||
let startStyles: {[key: string]: any}|null = null;
|
||||
let endStyles: {[key: string]: any}|null = null;
|
||||
if (Array.isArray(styles) && styles.length) {
|
||||
@ -47,7 +47,7 @@ export class SpecialCasedStyles {
|
||||
static initialStylesByElement = new WeakMap<any, {[key: string]: any}>();
|
||||
|
||||
private _state = SpecialCasedStylesState.Pending;
|
||||
private _initialStyles !: {[key: string]: any};
|
||||
private _initialStyles!: {[key: string]: any};
|
||||
|
||||
constructor(
|
||||
private _element: any, private _startStyles: {[key: string]: any}|null,
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AUTO_STYLE, AnimationMetadata, AnimationMetadataType, AnimationOptions, AnimationPlayer, ɵStyleData} from '@angular/animations';
|
||||
import {AnimationMetadata, AnimationMetadataType, AnimationOptions, AnimationPlayer, AUTO_STYLE, ɵStyleData} from '@angular/animations';
|
||||
|
||||
import {Ast} from '../dsl/animation_ast';
|
||||
import {buildAnimationAst} from '../dsl/animation_ast_builder';
|
||||
@ -34,7 +34,7 @@ export class TimelineAnimationEngine {
|
||||
const ast = buildAnimationAst(this._driver, metadata, errors);
|
||||
if (errors.length) {
|
||||
throw new Error(
|
||||
`Unable to build the animation due to the following errors: ${errors.join("\n")}`);
|
||||
`Unable to build the animation due to the following errors: ${errors.join('\n')}`);
|
||||
} else {
|
||||
this._animations[id] = ast;
|
||||
}
|
||||
@ -71,12 +71,13 @@ export class TimelineAnimationEngine {
|
||||
|
||||
if (errors.length) {
|
||||
throw new Error(
|
||||
`Unable to create the animation due to the following errors: ${errors.join("\n")}`);
|
||||
`Unable to create the animation due to the following errors: ${errors.join('\n')}`);
|
||||
}
|
||||
|
||||
autoStylesMap.forEach((styles, element) => {
|
||||
Object.keys(styles).forEach(
|
||||
prop => { styles[prop] = this._driver.computeStyle(element, prop, AUTO_STYLE); });
|
||||
Object.keys(styles).forEach(prop => {
|
||||
styles[prop] = this._driver.computeStyle(element, prop, AUTO_STYLE);
|
||||
});
|
||||
});
|
||||
|
||||
const players = instructions.map(i => {
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AUTO_STYLE, AnimationOptions, AnimationPlayer, NoopAnimationPlayer, ɵAnimationGroupPlayer as AnimationGroupPlayer, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations';
|
||||
import {AnimationOptions, AnimationPlayer, AUTO_STYLE, NoopAnimationPlayer, ɵAnimationGroupPlayer as AnimationGroupPlayer, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations';
|
||||
|
||||
import {AnimationTimelineInstruction} from '../dsl/animation_timeline_instruction';
|
||||
import {AnimationTransitionFactory} from '../dsl/animation_transition_factory';
|
||||
@ -13,7 +13,7 @@ import {AnimationTransitionInstruction} from '../dsl/animation_transition_instru
|
||||
import {AnimationTrigger} from '../dsl/animation_trigger';
|
||||
import {ElementInstructionMap} from '../dsl/element_instruction_map';
|
||||
import {AnimationStyleNormalizer} from '../dsl/style_normalization/animation_style_normalizer';
|
||||
import {ENTER_CLASSNAME, LEAVE_CLASSNAME, NG_ANIMATING_CLASSNAME, NG_ANIMATING_SELECTOR, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, copyObj, eraseStyles, iteratorToArray, setStyles} from '../util';
|
||||
import {copyObj, ENTER_CLASSNAME, eraseStyles, iteratorToArray, LEAVE_CLASSNAME, NG_ANIMATING_CLASSNAME, NG_ANIMATING_SELECTOR, NG_TRIGGER_CLASSNAME, NG_TRIGGER_SELECTOR, setStyles} from '../util';
|
||||
|
||||
import {AnimationDriver} from './animation_driver';
|
||||
import {getOrSetAsInMap, listenOnPlayer, makeAnimationEvent, normalizeKeyframes, optimizeGroupPlayer} from './shared';
|
||||
@ -71,7 +71,9 @@ export class StateValue {
|
||||
public value: string;
|
||||
public options: AnimationOptions;
|
||||
|
||||
get params(): {[key: string]: any} { return this.options.params as{[key: string]: any}; }
|
||||
get params(): {[key: string]: any} {
|
||||
return this.options.params as {[key: string]: any};
|
||||
}
|
||||
|
||||
constructor(input: any, public namespaceId: string = '') {
|
||||
const isObj = input && input.hasOwnProperty('value');
|
||||
@ -92,7 +94,7 @@ export class StateValue {
|
||||
absorbOptions(options: AnimationOptions) {
|
||||
const newParams = options.params;
|
||||
if (newParams) {
|
||||
const oldParams = this.options.params !;
|
||||
const oldParams = this.options.params!;
|
||||
Object.keys(newParams).forEach(prop => {
|
||||
if (oldParams[prop] == null) {
|
||||
oldParams[prop] = newParams[prop];
|
||||
@ -263,7 +265,9 @@ export class AnimationTransitionNamespace {
|
||||
|
||||
if (!isFallbackTransition) {
|
||||
addClass(element, QUEUED_CLASSNAME);
|
||||
player.onStart(() => { removeClass(element, QUEUED_CLASSNAME); });
|
||||
player.onStart(() => {
|
||||
removeClass(element, QUEUED_CLASSNAME);
|
||||
});
|
||||
}
|
||||
|
||||
player.onDone(() => {
|
||||
@ -290,11 +294,14 @@ export class AnimationTransitionNamespace {
|
||||
deregister(name: string) {
|
||||
delete this._triggers[name];
|
||||
|
||||
this._engine.statesByElement.forEach((stateMap, element) => { delete stateMap[name]; });
|
||||
this._engine.statesByElement.forEach((stateMap, element) => {
|
||||
delete stateMap[name];
|
||||
});
|
||||
|
||||
this._elementListeners.forEach((listeners, element) => {
|
||||
this._elementListeners.set(
|
||||
element, listeners.filter(entry => { return entry.name != name; }));
|
||||
this._elementListeners.set(element, listeners.filter(entry => {
|
||||
return entry.name != name;
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@ -372,7 +379,7 @@ export class AnimationTransitionNamespace {
|
||||
|
||||
const trigger = this._triggers[triggerName];
|
||||
const transition = trigger.fallbackTransition;
|
||||
const elementStates = this._engine.statesByElement.get(element) !;
|
||||
const elementStates = this._engine.statesByElement.get(element)!;
|
||||
const fromState = elementStates[triggerName] || DEFAULT_STATE_VALUE;
|
||||
const toState = new StateValue(VOID_VALUE);
|
||||
const player = new TransitionAnimationPlayer(this.id, triggerName, element);
|
||||
@ -448,7 +455,9 @@ export class AnimationTransitionNamespace {
|
||||
}
|
||||
}
|
||||
|
||||
insertNode(element: any, parent: any): void { addClass(element, this._hostClassName); }
|
||||
insertNode(element: any, parent: any): void {
|
||||
addClass(element, this._hostClassName);
|
||||
}
|
||||
|
||||
drainQueuedTransitions(microtaskId: number): QueueInstruction[] {
|
||||
const instructions: QueueInstruction[] = [];
|
||||
@ -538,7 +547,9 @@ export class TransitionAnimationEngine {
|
||||
public onRemovalComplete = (element: any, context: any) => {};
|
||||
|
||||
/** @internal */
|
||||
_onRemovalComplete(element: any, context: any) { this.onRemovalComplete(element, context); }
|
||||
_onRemovalComplete(element: any, context: any) {
|
||||
this.onRemovalComplete(element, context);
|
||||
}
|
||||
|
||||
constructor(
|
||||
public bodyNode: any, public driver: AnimationDriver,
|
||||
@ -631,7 +642,9 @@ export class TransitionAnimationEngine {
|
||||
this.afterFlushAnimationsDone(() => ns.destroy(context));
|
||||
}
|
||||
|
||||
private _fetchNamespace(id: string) { return this._namespaceLookup[id]; }
|
||||
private _fetchNamespace(id: string) {
|
||||
return this._namespaceLookup[id];
|
||||
}
|
||||
|
||||
fetchNamespacesByElement(element: any): Set<AnimationTransitionNamespace> {
|
||||
// normally there should only be one namespace per element, however
|
||||
@ -704,7 +717,9 @@ export class TransitionAnimationEngine {
|
||||
}
|
||||
}
|
||||
|
||||
collectEnterElement(element: any) { this.collectedEnterElements.push(element); }
|
||||
collectEnterElement(element: any) {
|
||||
this.collectedEnterElements.push(element);
|
||||
}
|
||||
|
||||
markElementAsDisabled(element: any, value: boolean) {
|
||||
if (value) {
|
||||
@ -740,11 +755,8 @@ export class TransitionAnimationEngine {
|
||||
|
||||
markElementAsRemoved(namespaceId: string, element: any, hasAnimation?: boolean, context?: any) {
|
||||
this.collectedLeaveElements.push(element);
|
||||
element[REMOVAL_FLAG] = {
|
||||
namespaceId,
|
||||
setForRemoval: context, hasAnimation,
|
||||
removedBeforeQueried: false
|
||||
};
|
||||
element[REMOVAL_FLAG] =
|
||||
{namespaceId, setForRemoval: context, hasAnimation, removedBeforeQueried: false};
|
||||
}
|
||||
|
||||
listen(
|
||||
@ -876,7 +888,9 @@ export class TransitionAnimationEngine {
|
||||
this._whenQuietFns = [];
|
||||
|
||||
if (players.length) {
|
||||
optimizeGroupPlayer(players).onDone(() => { quietFns.forEach(fn => fn()); });
|
||||
optimizeGroupPlayer(players).onDone(() => {
|
||||
quietFns.forEach(fn => fn());
|
||||
});
|
||||
} else {
|
||||
quietFns.forEach(fn => fn());
|
||||
}
|
||||
@ -950,16 +964,18 @@ export class TransitionAnimationEngine {
|
||||
|
||||
cleanupFns.push(() => {
|
||||
enterNodeMap.forEach((nodes, root) => {
|
||||
const className = enterNodeMapIds.get(root) !;
|
||||
const className = enterNodeMapIds.get(root)!;
|
||||
nodes.forEach(node => removeClass(node, className));
|
||||
});
|
||||
|
||||
leaveNodeMap.forEach((nodes, root) => {
|
||||
const className = leaveNodeMapIds.get(root) !;
|
||||
const className = leaveNodeMapIds.get(root)!;
|
||||
nodes.forEach(node => removeClass(node, className));
|
||||
});
|
||||
|
||||
allLeaveNodes.forEach(element => { this.processLeaveNode(element); });
|
||||
allLeaveNodes.forEach(element => {
|
||||
this.processLeaveNode(element);
|
||||
});
|
||||
});
|
||||
|
||||
const allPlayers: TransitionAnimationPlayer[] = [];
|
||||
@ -981,10 +997,10 @@ export class TransitionAnimationEngine {
|
||||
}
|
||||
|
||||
const nodeIsOrphaned = !bodyNode || !this.driver.containsElement(bodyNode, element);
|
||||
const leaveClassName = leaveNodeMapIds.get(element) !;
|
||||
const enterClassName = enterNodeMapIds.get(element) !;
|
||||
const leaveClassName = leaveNodeMapIds.get(element)!;
|
||||
const enterClassName = enterNodeMapIds.get(element)!;
|
||||
const instruction = this._buildInstruction(
|
||||
entry, subTimelines, enterClassName, leaveClassName, nodeIsOrphaned) !;
|
||||
entry, subTimelines, enterClassName, leaveClassName, nodeIsOrphaned)!;
|
||||
if (instruction.errors && instruction.errors.length) {
|
||||
erroneousTransitions.push(instruction);
|
||||
return;
|
||||
@ -1029,7 +1045,7 @@ export class TransitionAnimationEngine {
|
||||
instruction.preStyleProps.forEach((stringMap, element) => {
|
||||
const props = Object.keys(stringMap);
|
||||
if (props.length) {
|
||||
let setVal: Set<string> = allPreStyleElements.get(element) !;
|
||||
let setVal: Set<string> = allPreStyleElements.get(element)!;
|
||||
if (!setVal) {
|
||||
allPreStyleElements.set(element, setVal = new Set<string>());
|
||||
}
|
||||
@ -1039,7 +1055,7 @@ export class TransitionAnimationEngine {
|
||||
|
||||
instruction.postStyleProps.forEach((stringMap, element) => {
|
||||
const props = Object.keys(stringMap);
|
||||
let setVal: Set<string> = allPostStyleElements.get(element) !;
|
||||
let setVal: Set<string> = allPostStyleElements.get(element)!;
|
||||
if (!setVal) {
|
||||
allPostStyleElements.set(element, setVal = new Set<string>());
|
||||
}
|
||||
@ -1052,7 +1068,7 @@ export class TransitionAnimationEngine {
|
||||
const errors: string[] = [];
|
||||
erroneousTransitions.forEach(instruction => {
|
||||
errors.push(`@${instruction.triggerName} has failed due to:\n`);
|
||||
instruction.errors !.forEach(error => errors.push(`- ${error}\n`));
|
||||
instruction.errors!.forEach(error => errors.push(`- ${error}\n`));
|
||||
});
|
||||
|
||||
allPlayers.forEach(player => player.destroy());
|
||||
@ -1116,7 +1132,7 @@ export class TransitionAnimationEngine {
|
||||
replaceNodes.forEach(node => {
|
||||
const post = postStylesMap.get(node);
|
||||
const pre = preStylesMap.get(node);
|
||||
postStylesMap.set(node, { ...post, ...pre } as any);
|
||||
postStylesMap.set(node, {...post, ...pre} as any);
|
||||
});
|
||||
|
||||
const rootPlayers: TransitionAnimationPlayer[] = [];
|
||||
@ -1274,9 +1290,13 @@ export class TransitionAnimationEngine {
|
||||
return this._fetchNamespace(namespaceId).elementContainsData(element) || containsData;
|
||||
}
|
||||
|
||||
afterFlush(callback: () => any) { this._flushFns.push(callback); }
|
||||
afterFlush(callback: () => any) {
|
||||
this._flushFns.push(callback);
|
||||
}
|
||||
|
||||
afterFlushAnimationsDone(callback: () => any) { this._whenQuietFns.push(callback); }
|
||||
afterFlushAnimationsDone(callback: () => any) {
|
||||
this._whenQuietFns.push(callback);
|
||||
}
|
||||
|
||||
private _getPreviousPlayers(
|
||||
element: string, isQueriedElement: boolean, namespaceId?: string, triggerName?: string,
|
||||
@ -1413,8 +1433,9 @@ export class TransitionAnimationEngine {
|
||||
|
||||
// this basically makes all of the callbacks for sub element animations
|
||||
// be dependent on the upper players for when they finish
|
||||
allSubElements.forEach(
|
||||
element => { getOrSetAsInMap(skippedPlayersMap, element, []).push(player); });
|
||||
allSubElements.forEach(element => {
|
||||
getOrSetAsInMap(skippedPlayersMap, element, []).push(player);
|
||||
});
|
||||
|
||||
return player;
|
||||
}
|
||||
@ -1441,7 +1462,7 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
||||
private _queuedCallbacks: {[name: string]: (() => any)[]} = {};
|
||||
public readonly destroyed = false;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
public parentPlayer !: AnimationPlayer;
|
||||
public parentPlayer!: AnimationPlayer;
|
||||
|
||||
public markedForDestroy: boolean = false;
|
||||
public disabled = false;
|
||||
@ -1462,17 +1483,21 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
||||
this._queuedCallbacks = {};
|
||||
this._containsRealPlayer = true;
|
||||
this.overrideTotalTime(player.totalTime);
|
||||
(this as{queued: boolean}).queued = false;
|
||||
(this as {queued: boolean}).queued = false;
|
||||
}
|
||||
|
||||
getRealPlayer() { return this._player; }
|
||||
getRealPlayer() {
|
||||
return this._player;
|
||||
}
|
||||
|
||||
overrideTotalTime(totalTime: number) { (this as any).totalTime = totalTime; }
|
||||
overrideTotalTime(totalTime: number) {
|
||||
(this as any).totalTime = totalTime;
|
||||
}
|
||||
|
||||
syncPlayerEvents(player: AnimationPlayer) {
|
||||
const p = this._player as any;
|
||||
if (p.triggerCallback) {
|
||||
player.onStart(() => p.triggerCallback !('start'));
|
||||
player.onStart(() => p.triggerCallback!('start'));
|
||||
}
|
||||
player.onDone(() => this.finish());
|
||||
player.onDestroy(() => this.destroy());
|
||||
@ -1503,24 +1528,38 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
||||
this._player.onDestroy(fn);
|
||||
}
|
||||
|
||||
init(): void { this._player.init(); }
|
||||
init(): void {
|
||||
this._player.init();
|
||||
}
|
||||
|
||||
hasStarted(): boolean { return this.queued ? false : this._player.hasStarted(); }
|
||||
hasStarted(): boolean {
|
||||
return this.queued ? false : this._player.hasStarted();
|
||||
}
|
||||
|
||||
play(): void { !this.queued && this._player.play(); }
|
||||
play(): void {
|
||||
!this.queued && this._player.play();
|
||||
}
|
||||
|
||||
pause(): void { !this.queued && this._player.pause(); }
|
||||
pause(): void {
|
||||
!this.queued && this._player.pause();
|
||||
}
|
||||
|
||||
restart(): void { !this.queued && this._player.restart(); }
|
||||
restart(): void {
|
||||
!this.queued && this._player.restart();
|
||||
}
|
||||
|
||||
finish(): void { this._player.finish(); }
|
||||
finish(): void {
|
||||
this._player.finish();
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
(this as{destroyed: boolean}).destroyed = true;
|
||||
(this as {destroyed: boolean}).destroyed = true;
|
||||
this._player.destroy();
|
||||
}
|
||||
|
||||
reset(): void { !this.queued && this._player.reset(); }
|
||||
reset(): void {
|
||||
!this.queued && this._player.reset();
|
||||
}
|
||||
|
||||
setPosition(p: any): void {
|
||||
if (!this.queued) {
|
||||
@ -1528,7 +1567,9 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
getPosition(): number { return this.queued ? 0 : this._player.getPosition(); }
|
||||
getPosition(): number {
|
||||
return this.queued ? 0 : this._player.getPosition();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
triggerCallback(phaseName: string): void {
|
||||
@ -1539,7 +1580,7 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
function deleteOrUnsetInMap(map: Map<any, any[]>| {[key: string]: any}, key: any, value: any) {
|
||||
function deleteOrUnsetInMap(map: Map<any, any[]>|{[key: string]: any}, key: any, value: any) {
|
||||
let currentValues: any[]|null|undefined;
|
||||
if (map instanceof Map) {
|
||||
currentValues = map.get(key);
|
||||
@ -1661,7 +1702,7 @@ function buildRootMap(roots: any[], nodes: any[]): Map<any, any[]> {
|
||||
nodes.forEach(node => {
|
||||
const root = getRoot(node);
|
||||
if (root !== NULL_NODE) {
|
||||
rootMap.get(root) !.push(node);
|
||||
rootMap.get(root)!.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
@ -1742,7 +1783,7 @@ function replacePostStylesAsPre(
|
||||
|
||||
let preEntry = allPreStyleElements.get(element);
|
||||
if (preEntry) {
|
||||
postEntry.forEach(data => preEntry !.add(data));
|
||||
postEntry.forEach(data => preEntry!.add(data));
|
||||
} else {
|
||||
allPreStyleElements.set(element, postEntry);
|
||||
}
|
||||
|
@ -19,13 +19,17 @@ export class WebAnimationsDriver implements AnimationDriver {
|
||||
private _isNativeImpl = /\{\s*\[native\s+code\]\s*\}/.test(getElementAnimateFn().toString());
|
||||
private _cssKeyframesDriver = new CssKeyframesDriver();
|
||||
|
||||
validateStyleProperty(prop: string): boolean { return validateStyleProperty(prop); }
|
||||
validateStyleProperty(prop: string): boolean {
|
||||
return validateStyleProperty(prop);
|
||||
}
|
||||
|
||||
matchesElement(element: any, selector: string): boolean {
|
||||
return matchesElement(element, selector);
|
||||
}
|
||||
|
||||
containsElement(elm1: any, elm2: any): boolean { return containsElement(elm1, elm2); }
|
||||
containsElement(elm1: any, elm2: any): boolean {
|
||||
return containsElement(elm1, elm2);
|
||||
}
|
||||
|
||||
query(element: any, selector: string, multi: boolean): any[] {
|
||||
return invokeQuery(element, selector, multi);
|
||||
@ -35,7 +39,9 @@ export class WebAnimationsDriver implements AnimationDriver {
|
||||
return (window.getComputedStyle(element) as any)[prop] as string;
|
||||
}
|
||||
|
||||
overrideWebAnimationsSupport(supported: boolean) { this._isNativeImpl = supported; }
|
||||
overrideWebAnimationsSupport(supported: boolean) {
|
||||
this._isNativeImpl = supported;
|
||||
}
|
||||
|
||||
animate(
|
||||
element: any, keyframes: ɵStyleData[], duration: number, delay: number, easing: string,
|
||||
@ -47,7 +53,7 @@ export class WebAnimationsDriver implements AnimationDriver {
|
||||
}
|
||||
|
||||
const fill = delay == 0 ? 'both' : 'forwards';
|
||||
const playerOptions: {[key: string]: string | number} = {duration, delay, fill};
|
||||
const playerOptions: {[key: string]: string|number} = {duration, delay, fill};
|
||||
// we check for this to avoid having a null|undefined value be present
|
||||
// for the easing (which results in an error for certain browsers #9752)
|
||||
if (easing) {
|
||||
|
@ -23,18 +23,18 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
||||
private _started = false;
|
||||
private _destroyed = false;
|
||||
// TODO(issue/24571): remove '!'.
|
||||
private _finalKeyframe !: {[key: string]: string | number};
|
||||
private _finalKeyframe!: {[key: string]: string|number};
|
||||
|
||||
// TODO(issue/24571): remove '!'.
|
||||
public readonly domPlayer !: DOMAnimation;
|
||||
public readonly domPlayer!: DOMAnimation;
|
||||
public time = 0;
|
||||
|
||||
public parentPlayer: AnimationPlayer|null = null;
|
||||
public currentSnapshot: {[styleName: string]: string | number} = {};
|
||||
public currentSnapshot: {[styleName: string]: string|number} = {};
|
||||
|
||||
constructor(
|
||||
public element: any, public keyframes: {[key: string]: string | number}[],
|
||||
public options: {[key: string]: string | number},
|
||||
public element: any, public keyframes: {[key: string]: string|number}[],
|
||||
public options: {[key: string]: string|number},
|
||||
private _specialStyles?: SpecialCasedStyles|null) {
|
||||
this._duration = <number>options['duration'];
|
||||
this._delay = <number>options['delay'] || 0;
|
||||
@ -59,7 +59,7 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
||||
this._initialized = true;
|
||||
|
||||
const keyframes = this.keyframes;
|
||||
(this as{domPlayer: DOMAnimation}).domPlayer =
|
||||
(this as {domPlayer: DOMAnimation}).domPlayer =
|
||||
this._triggerWebAnimation(this.element, keyframes, this.options);
|
||||
this._finalKeyframe = keyframes.length ? keyframes[keyframes.length - 1] : {};
|
||||
this.domPlayer.addEventListener('finish', () => this._onFinish());
|
||||
@ -81,11 +81,17 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
||||
return element['animate'](keyframes, options) as DOMAnimation;
|
||||
}
|
||||
|
||||
onStart(fn: () => void): void { this._onStartFns.push(fn); }
|
||||
onStart(fn: () => void): void {
|
||||
this._onStartFns.push(fn);
|
||||
}
|
||||
|
||||
onDone(fn: () => void): void { this._onDoneFns.push(fn); }
|
||||
onDone(fn: () => void): void {
|
||||
this._onDoneFns.push(fn);
|
||||
}
|
||||
|
||||
onDestroy(fn: () => void): void { this._onDestroyFns.push(fn); }
|
||||
onDestroy(fn: () => void): void {
|
||||
this._onDestroyFns.push(fn);
|
||||
}
|
||||
|
||||
play(): void {
|
||||
this._buildPlayer();
|
||||
@ -132,7 +138,9 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
||||
this.play();
|
||||
}
|
||||
|
||||
hasStarted(): boolean { return this._started; }
|
||||
hasStarted(): boolean {
|
||||
return this._started;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
if (!this._destroyed) {
|
||||
@ -147,14 +155,20 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
setPosition(p: number): void { this.domPlayer.currentTime = p * this.time; }
|
||||
setPosition(p: number): void {
|
||||
this.domPlayer.currentTime = p * this.time;
|
||||
}
|
||||
|
||||
getPosition(): number { return this.domPlayer.currentTime / this.time; }
|
||||
getPosition(): number {
|
||||
return this.domPlayer.currentTime / this.time;
|
||||
}
|
||||
|
||||
get totalTime(): number { return this._delay + this._duration; }
|
||||
get totalTime(): number {
|
||||
return this._delay + this._duration;
|
||||
}
|
||||
|
||||
beforeDestroy() {
|
||||
const styles: {[key: string]: string | number} = {};
|
||||
const styles: {[key: string]: string|number} = {};
|
||||
if (this.hasStarted()) {
|
||||
Object.keys(this._finalKeyframe).forEach(prop => {
|
||||
if (prop != 'offset') {
|
||||
|
@ -23,7 +23,7 @@ export const NG_TRIGGER_SELECTOR = '.ng-trigger';
|
||||
export const NG_ANIMATING_CLASSNAME = 'ng-animating';
|
||||
export const NG_ANIMATING_SELECTOR = '.ng-animating';
|
||||
|
||||
export function resolveTimingValue(value: string | number) {
|
||||
export function resolveTimingValue(value: string|number) {
|
||||
if (typeof value == 'number') return value;
|
||||
|
||||
const matches = value.match(/^(-?[\.\d]+)(m?s)/);
|
||||
@ -42,14 +42,14 @@ function _convertTimeValueToMS(value: number, unit: string): number {
|
||||
}
|
||||
|
||||
export function resolveTiming(
|
||||
timings: string | number | AnimateTimings, errors: any[], allowNegativeValues?: boolean) {
|
||||
timings: string|number|AnimateTimings, errors: any[], allowNegativeValues?: boolean) {
|
||||
return timings.hasOwnProperty('duration') ?
|
||||
<AnimateTimings>timings :
|
||||
parseTimeExpression(<string|number>timings, errors, allowNegativeValues);
|
||||
}
|
||||
|
||||
function parseTimeExpression(
|
||||
exp: string | number, errors: string[], allowNegativeValues?: boolean): AnimateTimings {
|
||||
exp: string|number, errors: string[], allowNegativeValues?: boolean): AnimateTimings {
|
||||
const regex = /^(-?[\.\d]+)(m?s)(?:\s+(-?[\.\d]+)(m?s))?(?:\s+([-a-z]+(?:\(.+?\))?))?$/i;
|
||||
let duration: number;
|
||||
let delay: number = 0;
|
||||
@ -97,11 +97,13 @@ function parseTimeExpression(
|
||||
|
||||
export function copyObj(
|
||||
obj: {[key: string]: any}, destination: {[key: string]: any} = {}): {[key: string]: any} {
|
||||
Object.keys(obj).forEach(prop => { destination[prop] = obj[prop]; });
|
||||
Object.keys(obj).forEach(prop => {
|
||||
destination[prop] = obj[prop];
|
||||
});
|
||||
return destination;
|
||||
}
|
||||
|
||||
export function normalizeStyles(styles: ɵStyleData | ɵStyleData[]): ɵStyleData {
|
||||
export function normalizeStyles(styles: ɵStyleData|ɵStyleData[]): ɵStyleData {
|
||||
const normalizedStyles: ɵStyleData = {};
|
||||
if (Array.isArray(styles)) {
|
||||
styles.forEach(data => copyStyles(data, false, normalizedStyles));
|
||||
@ -186,8 +188,8 @@ export function eraseStyles(element: any, styles: ɵStyleData) {
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeAnimationEntry(steps: AnimationMetadata | AnimationMetadata[]):
|
||||
AnimationMetadata {
|
||||
export function normalizeAnimationEntry(steps: AnimationMetadata|
|
||||
AnimationMetadata[]): AnimationMetadata {
|
||||
if (Array.isArray(steps)) {
|
||||
if (steps.length == 1) return steps[0];
|
||||
return sequence(steps);
|
||||
@ -196,7 +198,7 @@ export function normalizeAnimationEntry(steps: AnimationMetadata | AnimationMeta
|
||||
}
|
||||
|
||||
export function validateStyleParams(
|
||||
value: string | number, options: AnimationOptions, errors: any[]) {
|
||||
value: string|number, options: AnimationOptions, errors: any[]) {
|
||||
const params = options.params || {};
|
||||
const matches = extractStyleParams(value);
|
||||
if (matches.length) {
|
||||
@ -211,7 +213,7 @@ export function validateStyleParams(
|
||||
|
||||
const PARAM_REGEX =
|
||||
new RegExp(`${SUBSTITUTION_EXPR_START}\\s*(.+?)\\s*${SUBSTITUTION_EXPR_END}`, 'g');
|
||||
export function extractStyleParams(value: string | number): string[] {
|
||||
export function extractStyleParams(value: string|number): string[] {
|
||||
let params: string[] = [];
|
||||
if (typeof value === 'string') {
|
||||
let match: any;
|
||||
@ -224,7 +226,7 @@ export function extractStyleParams(value: string | number): string[] {
|
||||
}
|
||||
|
||||
export function interpolateParams(
|
||||
value: string | number, params: {[name: string]: any}, errors: any[]): string|number {
|
||||
value: string|number, params: {[name: string]: any}, errors: any[]): string|number {
|
||||
const original = value.toString();
|
||||
const str = original.replace(PARAM_REGEX, (_, varName) => {
|
||||
let localVal = params[varName];
|
||||
@ -297,7 +299,9 @@ export function balancePreviousStylesIntoKeyframes(
|
||||
// tslint:disable-next-line
|
||||
for (var i = 1; i < keyframes.length; i++) {
|
||||
let kf = keyframes[i];
|
||||
missingStyleProps.forEach(function(prop) { kf[prop] = computeStyle(element, prop); });
|
||||
missingStyleProps.forEach(function(prop) {
|
||||
kf[prop] = computeStyle(element, prop);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AUTO_STYLE, AnimationMetadata, AnimationMetadataType, AnimationOptions, animate, animation, group, keyframes, query, sequence, state, style, transition, trigger, useAnimation, ɵStyleData} from '@angular/animations';
|
||||
import {animate, animation, AnimationMetadata, AnimationMetadataType, AnimationOptions, AUTO_STYLE, group, keyframes, query, sequence, state, style, transition, trigger, useAnimation, ɵStyleData} from '@angular/animations';
|
||||
|
||||
import {Animation} from '../../src/dsl/animation';
|
||||
import {buildAnimationAst} from '../../src/dsl/animation_ast_builder';
|
||||
@ -35,7 +35,9 @@ function createDiv() {
|
||||
rootElement.appendChild(subElement2);
|
||||
});
|
||||
|
||||
afterEach(() => { document.body.removeChild(rootElement); });
|
||||
afterEach(() => {
|
||||
document.body.removeChild(rootElement);
|
||||
});
|
||||
|
||||
describe('validation', () => {
|
||||
it('should throw an error if one or more but not all keyframes() styles contain offsets',
|
||||
@ -45,7 +47,9 @@ function createDiv() {
|
||||
style({opacity: 1, offset: 1}),
|
||||
]));
|
||||
|
||||
expect(() => { validateAndThrowAnimationSequence(steps); })
|
||||
expect(() => {
|
||||
validateAndThrowAnimationSequence(steps);
|
||||
})
|
||||
.toThrowError(
|
||||
/Not all style\(\) steps within the declared keyframes\(\) contain offsets/);
|
||||
});
|
||||
@ -96,7 +100,9 @@ function createDiv() {
|
||||
]))
|
||||
]);
|
||||
|
||||
expect(() => { validateAndThrowAnimationSequence(steps); })
|
||||
expect(() => {
|
||||
validateAndThrowAnimationSequence(steps);
|
||||
})
|
||||
.toThrowError(
|
||||
/The CSS property "opacity" that exists between the times of "0ms" and "2000ms" is also being animated in a parallel animation between the times of "0ms" and "1500ms"/);
|
||||
});
|
||||
@ -191,7 +197,9 @@ function createDiv() {
|
||||
})),
|
||||
];
|
||||
|
||||
expect(() => { validateAndThrowAnimationSequence(steps); })
|
||||
expect(() => {
|
||||
validateAndThrowAnimationSequence(steps);
|
||||
})
|
||||
.toThrowError(
|
||||
/state\("final", ...\) must define default values for all the following style substitutions: one, two, three/);
|
||||
|
||||
@ -203,7 +211,9 @@ function createDiv() {
|
||||
}),
|
||||
{params: {redColor: 'maroon'}})];
|
||||
|
||||
expect(() => { validateAndThrowAnimationSequence(steps2); })
|
||||
expect(() => {
|
||||
validateAndThrowAnimationSequence(steps2);
|
||||
})
|
||||
.toThrowError(
|
||||
/state\("panfinal", ...\) must define default values for all the following style substitutions: greyColor/);
|
||||
});
|
||||
@ -211,7 +221,9 @@ function createDiv() {
|
||||
it('should throw an error if an invalid CSS property is used in the animation', () => {
|
||||
const steps = [animate(1000, style({abc: '500px'}))];
|
||||
|
||||
expect(() => { validateAndThrowAnimationSequence(steps); })
|
||||
expect(() => {
|
||||
validateAndThrowAnimationSequence(steps);
|
||||
})
|
||||
.toThrowError(
|
||||
/The provided animation property "abc" is not a supported CSS property for animations/);
|
||||
});
|
||||
@ -388,7 +400,7 @@ function createDiv() {
|
||||
let players = invokeAnimationSequence(rootElement, steps);
|
||||
expect(players.length).toEqual(1);
|
||||
|
||||
let p1 = players.pop() !;
|
||||
let p1 = players.pop()!;
|
||||
expect(p1.duration).toEqual(1500);
|
||||
expect(p1.keyframes).toEqual([
|
||||
{width: '*', offset: 0},
|
||||
@ -405,7 +417,7 @@ function createDiv() {
|
||||
players = invokeAnimationSequence(rootElement, steps);
|
||||
expect(players.length).toEqual(1);
|
||||
|
||||
p1 = players.pop() !;
|
||||
p1 = players.pop()!;
|
||||
expect(p1.duration).toEqual(1000);
|
||||
expect(p1.keyframes).toEqual([
|
||||
{width: '100px', offset: 0},
|
||||
@ -876,7 +888,9 @@ function createDiv() {
|
||||
const steps =
|
||||
[query('somethingFake', [style({opacity: 0}), animate(1000, style({opacity: 1}))])];
|
||||
|
||||
expect(() => { invokeAnimationSequence(rootElement, steps); })
|
||||
expect(() => {
|
||||
invokeAnimationSequence(rootElement, steps);
|
||||
})
|
||||
.toThrowError(
|
||||
/`query\("somethingFake"\)` returned zero elements\. \(Use `query\("somethingFake", \{ optional: true \}\)` if you wish to allow this\.\)/);
|
||||
});
|
||||
@ -887,13 +901,17 @@ function createDiv() {
|
||||
'somethingFake', [style({opacity: 0}), animate(1000, style({opacity: 1}))],
|
||||
{optional: true})];
|
||||
|
||||
expect(() => { invokeAnimationSequence(rootElement, steps); }).not.toThrow();
|
||||
expect(() => {
|
||||
invokeAnimationSequence(rootElement, steps);
|
||||
}).not.toThrow();
|
||||
|
||||
const steps2 = [query(
|
||||
'fakeSomethings', [style({opacity: 0}), animate(1000, style({opacity: 1}))],
|
||||
{optional: true})];
|
||||
|
||||
expect(() => { invokeAnimationSequence(rootElement, steps2); }).not.toThrow();
|
||||
expect(() => {
|
||||
invokeAnimationSequence(rootElement, steps2);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should delay the query operation if a delay option is provided', () => {
|
||||
@ -1025,8 +1043,7 @@ function createDiv() {
|
||||
|
||||
const players = invokeAnimationSequence(rootElement, steps, {}, fromStyles, toStyles);
|
||||
expect(players[0].keyframes).toEqual([
|
||||
{background: 'blue', offset: 0, easing: 'ease-out'},
|
||||
{background: 'red', offset: 1}
|
||||
{background: 'blue', offset: 0, easing: 'ease-out'}, {background: 'red', offset: 1}
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -1042,7 +1059,7 @@ function humanizeOffsets(keyframes: ɵStyleData[], digits: number = 3): ɵStyleD
|
||||
}
|
||||
|
||||
function invokeAnimationSequence(
|
||||
element: any, steps: AnimationMetadata | AnimationMetadata[], locals: {[key: string]: any} = {},
|
||||
element: any, steps: AnimationMetadata|AnimationMetadata[], locals: {[key: string]: any} = {},
|
||||
startingStyles: ɵStyleData[] = [], destinationStyles: ɵStyleData[] = [],
|
||||
subInstructions?: ElementInstructionMap): AnimationTimelineInstruction[] {
|
||||
const driver = new MockAnimationDriver();
|
||||
@ -1050,7 +1067,7 @@ function invokeAnimationSequence(
|
||||
.buildTimelines(element, startingStyles, destinationStyles, locals, subInstructions);
|
||||
}
|
||||
|
||||
function validateAndThrowAnimationSequence(steps: AnimationMetadata | AnimationMetadata[]) {
|
||||
function validateAndThrowAnimationSequence(steps: AnimationMetadata|AnimationMetadata[]) {
|
||||
const driver = new MockAnimationDriver();
|
||||
const errors: any[] = [];
|
||||
const ast = buildAnimationAst(driver, steps, errors);
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AnimationOptions, animate, state, style, transition} from '@angular/animations';
|
||||
import {animate, AnimationOptions, state, style, transition} from '@angular/animations';
|
||||
import {AnimationTransitionInstruction} from '@angular/animations/browser/src/dsl/animation_transition_instruction';
|
||||
import {AnimationTrigger} from '@angular/animations/browser/src/dsl/animation_trigger';
|
||||
|
||||
@ -25,7 +25,9 @@ import {makeTrigger} from '../shared';
|
||||
document.body.appendChild(element);
|
||||
});
|
||||
|
||||
afterEach(() => { document.body.removeChild(element); });
|
||||
afterEach(() => {
|
||||
document.body.removeChild(element);
|
||||
});
|
||||
|
||||
describe('trigger validation', () => {
|
||||
it('should group errors together for an animation trigger', () => {
|
||||
@ -36,8 +38,9 @@ import {makeTrigger} from '../shared';
|
||||
|
||||
it('should throw an error when a transition within a trigger contains an invalid expression',
|
||||
() => {
|
||||
expect(
|
||||
() => { makeTrigger('name', [transition('somethingThatIsWrong', animate(3333))]); })
|
||||
expect(() => {
|
||||
makeTrigger('name', [transition('somethingThatIsWrong', animate(3333))]);
|
||||
})
|
||||
.toThrowError(
|
||||
/- The provided transition expression "somethingThatIsWrong" is not supported/);
|
||||
});
|
||||
@ -78,7 +81,7 @@ import {makeTrigger} from '../shared';
|
||||
const result = makeTrigger(
|
||||
'name', [transition('a => b', animate(1234)), transition('b => c', animate(5678))]);
|
||||
|
||||
const trans = buildTransition(result, element, 'b', 'c') !;
|
||||
const trans = buildTransition(result, element, 'b', 'c')!;
|
||||
expect(trans.timelines.length).toEqual(1);
|
||||
const timeline = trans.timelines[0];
|
||||
expect(timeline.duration).toEqual(5678);
|
||||
@ -90,13 +93,13 @@ import {makeTrigger} from '../shared';
|
||||
transition('* => *', animate(9999))
|
||||
]);
|
||||
|
||||
let trans = buildTransition(result, element, 'b', 'c') !;
|
||||
let trans = buildTransition(result, element, 'b', 'c')!;
|
||||
expect(trans.timelines[0].duration).toEqual(5678);
|
||||
|
||||
trans = buildTransition(result, element, 'a', 'b') !;
|
||||
trans = buildTransition(result, element, 'a', 'b')!;
|
||||
expect(trans.timelines[0].duration).toEqual(1234);
|
||||
|
||||
trans = buildTransition(result, element, 'c', 'c') !;
|
||||
trans = buildTransition(result, element, 'c', 'c')!;
|
||||
expect(trans.timelines[0].duration).toEqual(9999);
|
||||
});
|
||||
|
||||
@ -110,23 +113,23 @@ import {makeTrigger} from '../shared';
|
||||
it('should support bi-directional transition expressions', () => {
|
||||
const result = makeTrigger('name', [transition('a <=> b', animate(2222))]);
|
||||
|
||||
const t1 = buildTransition(result, element, 'a', 'b') !;
|
||||
const t1 = buildTransition(result, element, 'a', 'b')!;
|
||||
expect(t1.timelines[0].duration).toEqual(2222);
|
||||
|
||||
const t2 = buildTransition(result, element, 'b', 'a') !;
|
||||
const t2 = buildTransition(result, element, 'b', 'a')!;
|
||||
expect(t2.timelines[0].duration).toEqual(2222);
|
||||
});
|
||||
|
||||
it('should support multiple transition statements in one string', () => {
|
||||
const result = makeTrigger('name', [transition('a => b, b => a, c => *', animate(1234))]);
|
||||
|
||||
const t1 = buildTransition(result, element, 'a', 'b') !;
|
||||
const t1 = buildTransition(result, element, 'a', 'b')!;
|
||||
expect(t1.timelines[0].duration).toEqual(1234);
|
||||
|
||||
const t2 = buildTransition(result, element, 'b', 'a') !;
|
||||
const t2 = buildTransition(result, element, 'b', 'a')!;
|
||||
expect(t2.timelines[0].duration).toEqual(1234);
|
||||
|
||||
const t3 = buildTransition(result, element, 'c', 'a') !;
|
||||
const t3 = buildTransition(result, element, 'c', 'a')!;
|
||||
expect(t3.timelines[0].duration).toEqual(1234);
|
||||
});
|
||||
|
||||
@ -138,7 +141,7 @@ import {makeTrigger} from '../shared';
|
||||
'a => b', [style({height: '{{ a }}'}), animate(1000, style({height: '{{ b }}'}))],
|
||||
buildParams({a: '100px', b: '200px'}))]);
|
||||
|
||||
const trans = buildTransition(result, element, 'a', 'b') !;
|
||||
const trans = buildTransition(result, element, 'a', 'b')!;
|
||||
const keyframes = trans.timelines[0].keyframes;
|
||||
expect(keyframes).toEqual([{height: '100px', offset: 0}, {height: '200px', offset: 1}]);
|
||||
});
|
||||
@ -153,7 +156,7 @@ import {makeTrigger} from '../shared';
|
||||
buildParams({a: '100px', b: '200px'}))]);
|
||||
|
||||
const trans =
|
||||
buildTransition(result, element, 'a', 'b', {}, buildParams({a: '300px'})) !;
|
||||
buildTransition(result, element, 'a', 'b', {}, buildParams({a: '300px'}))!;
|
||||
|
||||
const keyframes = trans.timelines[0].keyframes;
|
||||
expect(keyframes).toEqual(
|
||||
@ -167,7 +170,7 @@ import {makeTrigger} from '../shared';
|
||||
transition('true <=> false', animate(1234))
|
||||
]);
|
||||
|
||||
const trans = buildTransition(result, element, false, true) !;
|
||||
const trans = buildTransition(result, element, false, true)!;
|
||||
expect(trans.timelines[0].duration).toEqual(1234);
|
||||
});
|
||||
|
||||
@ -177,7 +180,7 @@ import {makeTrigger} from '../shared';
|
||||
transition('1 <=> 0', animate(4567))
|
||||
]);
|
||||
|
||||
const trans = buildTransition(result, element, false, true) !;
|
||||
const trans = buildTransition(result, element, false, true)!;
|
||||
expect(trans.timelines[0].duration).toEqual(4567);
|
||||
});
|
||||
|
||||
@ -188,7 +191,7 @@ import {makeTrigger} from '../shared';
|
||||
transition('1 <=> 0', animate(4567))
|
||||
]);
|
||||
|
||||
const trans = buildTransition(result, element, false, true) !;
|
||||
const trans = buildTransition(result, element, false, true)!;
|
||||
expect(trans.timelines[0].keyframes).toEqual([
|
||||
{offset: 0, color: 'red'}, {offset: 1, color: 'green'}
|
||||
]);
|
||||
@ -201,7 +204,7 @@ import {makeTrigger} from '../shared';
|
||||
transition('true <=> false', animate(4567))
|
||||
]);
|
||||
|
||||
const trans = buildTransition(result, element, false, true) !;
|
||||
const trans = buildTransition(result, element, false, true)!;
|
||||
expect(trans.timelines[0].keyframes).toEqual([
|
||||
{offset: 0, color: 'orange'}, {offset: 1, color: 'blue'}
|
||||
]);
|
||||
@ -214,7 +217,7 @@ import {makeTrigger} from '../shared';
|
||||
]);
|
||||
|
||||
expect(() => {
|
||||
const trans = buildTransition(result, element, false, true) !;
|
||||
const trans = buildTransition(result, element, false, true)!;
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
@ -222,14 +225,14 @@ import {makeTrigger} from '../shared';
|
||||
it('should alias the :enter transition as void => *', () => {
|
||||
const result = makeTrigger('name', [transition(':enter', animate(3333))]);
|
||||
|
||||
const trans = buildTransition(result, element, 'void', 'something') !;
|
||||
const trans = buildTransition(result, element, 'void', 'something')!;
|
||||
expect(trans.timelines[0].duration).toEqual(3333);
|
||||
});
|
||||
|
||||
it('should alias the :leave transition as * => void', () => {
|
||||
const result = makeTrigger('name', [transition(':leave', animate(3333))]);
|
||||
|
||||
const trans = buildTransition(result, element, 'something', 'void') !;
|
||||
const trans = buildTransition(result, element, 'something', 'void')!;
|
||||
expect(trans.timelines[0].duration).toEqual(3333);
|
||||
});
|
||||
});
|
||||
@ -242,12 +245,12 @@ function buildTransition(
|
||||
fromOptions?: AnimationOptions, toOptions?: AnimationOptions): AnimationTransitionInstruction|
|
||||
null {
|
||||
const params = toOptions && toOptions.params || {};
|
||||
const trans = trigger.matchTransition(fromState, toState, element, params) !;
|
||||
const trans = trigger.matchTransition(fromState, toState, element, params)!;
|
||||
if (trans) {
|
||||
const driver = new MockAnimationDriver();
|
||||
return trans.build(
|
||||
driver, element, fromState, toState, ENTER_CLASSNAME, LEAVE_CLASSNAME, fromOptions,
|
||||
toOptions) !;
|
||||
toOptions)!;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -16,13 +16,13 @@ import {WebAnimationsStyleNormalizer} from '../../../src/dsl/style_normalization
|
||||
expect(normalizer.normalizePropertyName('width', [])).toEqual('width');
|
||||
expect(normalizer.normalizePropertyName('border-width', [])).toEqual('borderWidth');
|
||||
expect(normalizer.normalizePropertyName('borderHeight', [])).toEqual('borderHeight');
|
||||
expect(normalizer.normalizePropertyName('-webkit-animation', [
|
||||
])).toEqual('WebkitAnimation');
|
||||
expect(normalizer.normalizePropertyName('-webkit-animation', []))
|
||||
.toEqual('WebkitAnimation');
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeStyleValue', () => {
|
||||
function normalize(prop: string, val: string | number): string {
|
||||
function normalize(prop: string, val: string|number): string {
|
||||
const errors: string[] = [];
|
||||
const result = normalizer.normalizeStyleValue(prop, prop, val, errors);
|
||||
if (errors.length) {
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {TestBed, fakeAsync, flushMicrotasks} from '@angular/core/testing';
|
||||
import {fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {CssKeyframesDriver} from '../../../src/render/css_keyframes/css_keyframes_driver';
|
||||
import {CssKeyframesPlayer} from '../../../src/render/css_keyframes/css_keyframes_player';
|
||||
@ -16,7 +16,7 @@ import {assertElementExistsInDom, createElement, findKeyframeDefinition, forceRe
|
||||
const CSS_KEYFRAME_RULE_TYPE = 7;
|
||||
|
||||
describe('CssKeyframesDriver tests', () => {
|
||||
if (isNode || typeof(window as any)['AnimationEvent'] == 'undefined') return;
|
||||
if (isNode || typeof (window as any)['AnimationEvent'] == 'undefined') return;
|
||||
|
||||
describe('building keyframes', () => {
|
||||
it('should build CSS keyframe style object containing the keyframe styles', () => {
|
||||
@ -28,7 +28,7 @@ describe('CssKeyframesDriver tests', () => {
|
||||
{opacity: 1, width: '200px', offset: 1},
|
||||
]);
|
||||
|
||||
const head = document.querySelector('head') !;
|
||||
const head = document.querySelector('head')!;
|
||||
head.appendChild(kfElm);
|
||||
forceReflow();
|
||||
|
||||
@ -67,7 +67,7 @@ describe('CssKeyframesDriver tests', () => {
|
||||
{width: '200px', offset: 1},
|
||||
]);
|
||||
|
||||
const head = document.querySelector('head') !;
|
||||
const head = document.querySelector('head')!;
|
||||
head.appendChild(kfElm);
|
||||
forceReflow();
|
||||
|
||||
@ -261,7 +261,7 @@ describe('CssKeyframesDriver tests', () => {
|
||||
|
||||
player.play();
|
||||
player.finish();
|
||||
player.beforeDestroy !();
|
||||
player.beforeDestroy!();
|
||||
expect(player.currentSnapshot).toEqual({
|
||||
width: '999px',
|
||||
height: '999px',
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {TestBed, fakeAsync, flushMicrotasks} from '@angular/core/testing';
|
||||
import {fakeAsync, flushMicrotasks, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {DirectStylePlayer} from '../../../src/render/css_keyframes/direct_style_player';
|
||||
|
||||
|
@ -13,7 +13,7 @@ import {assertStyle, createElement, makeAnimationEvent, supportsAnimationEventCr
|
||||
const EMPTY_FN = () => {};
|
||||
{
|
||||
describe('ElementAnimationStyleHandler', () => {
|
||||
if (isNode || typeof(window as any)['AnimationEvent'] == 'undefined') return;
|
||||
if (isNode || typeof (window as any)['AnimationEvent'] == 'undefined') return;
|
||||
|
||||
it('should add and remove an animation on to an element\'s styling', () => {
|
||||
const element = createElement();
|
||||
|
@ -10,7 +10,7 @@ export function forceReflow() {
|
||||
}
|
||||
|
||||
export function makeAnimationEvent(
|
||||
startOrEnd: 'start' | 'end', animationName: string, elapsedTime: number, timestamp?: number) {
|
||||
startOrEnd: 'start'|'end', animationName: string, elapsedTime: number, timestamp?: number) {
|
||||
const e = new AnimationEvent('animation' + startOrEnd, {animationName, elapsedTime});
|
||||
if (timestamp) {
|
||||
(e as any)._ngTestManualTimestamp = timestamp;
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AnimationMetadata, animate, style} from '@angular/animations';
|
||||
import {animate, AnimationMetadata, style} from '@angular/animations';
|
||||
|
||||
import {AnimationStyleNormalizer, NoopAnimationStyleNormalizer} from '../../src/dsl/style_normalization/animation_style_normalizer';
|
||||
import {AnimationDriver} from '../../src/render/animation_driver';
|
||||
@ -14,98 +14,101 @@ import {TimelineAnimationEngine} from '../../src/render/timeline_animation_engin
|
||||
import {MockAnimationDriver, MockAnimationPlayer} from '../../testing/src/mock_animation_driver';
|
||||
|
||||
(function() {
|
||||
const defaultDriver = new MockAnimationDriver();
|
||||
const defaultDriver = new MockAnimationDriver();
|
||||
|
||||
function makeEngine(body: any, driver?: AnimationDriver, normalizer?: AnimationStyleNormalizer) {
|
||||
return new TimelineAnimationEngine(
|
||||
body, driver || defaultDriver, normalizer || new NoopAnimationStyleNormalizer());
|
||||
}
|
||||
function makeEngine(body: any, driver?: AnimationDriver, normalizer?: AnimationStyleNormalizer) {
|
||||
return new TimelineAnimationEngine(
|
||||
body, driver || defaultDriver, normalizer || new NoopAnimationStyleNormalizer());
|
||||
}
|
||||
|
||||
// these tests are only mean't to be run within the DOM
|
||||
if (isNode) return;
|
||||
// these tests are only mean't to be run within the DOM
|
||||
if (isNode) return;
|
||||
|
||||
describe('TimelineAnimationEngine', () => {
|
||||
let element: any;
|
||||
describe('TimelineAnimationEngine', () => {
|
||||
let element: any;
|
||||
|
||||
beforeEach(() => {
|
||||
MockAnimationDriver.log = [];
|
||||
element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
});
|
||||
|
||||
afterEach(() => document.body.removeChild(element));
|
||||
|
||||
it('should animate a timeline', () => {
|
||||
const engine = makeEngine(getBodyNode());
|
||||
const steps = [style({height: 100}), animate(1000, style({height: 0}))];
|
||||
expect(MockAnimationDriver.log.length).toEqual(0);
|
||||
invokeAnimation(engine, element, steps);
|
||||
expect(MockAnimationDriver.log.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should not destroy timeline-based animations after they have finished', () => {
|
||||
const engine = makeEngine(getBodyNode());
|
||||
|
||||
const log: string[] = [];
|
||||
function capture(value: string) {
|
||||
return () => { log.push(value); };
|
||||
}
|
||||
|
||||
const steps = [style({height: 0}), animate(1000, style({height: 500}))];
|
||||
|
||||
const player = invokeAnimation(engine, element, steps);
|
||||
player.onDone(capture('done'));
|
||||
player.onDestroy(capture('destroy'));
|
||||
expect(log).toEqual([]);
|
||||
|
||||
player.finish();
|
||||
expect(log).toEqual(['done']);
|
||||
|
||||
player.destroy();
|
||||
expect(log).toEqual(['done', 'destroy']);
|
||||
});
|
||||
|
||||
it('should normalize the style values that are animateTransitioned within an a timeline animation',
|
||||
() => {
|
||||
const engine =
|
||||
makeEngine(getBodyNode(), defaultDriver, new SuffixNormalizer('-normalized'));
|
||||
|
||||
const steps = [
|
||||
style({width: '333px'}),
|
||||
animate(1000, style({width: '999px'})),
|
||||
];
|
||||
|
||||
const player = invokeAnimation(engine, element, steps) as MockAnimationPlayer;
|
||||
expect(player.keyframes).toEqual([
|
||||
{'width-normalized': '333px-normalized', offset: 0},
|
||||
{'width-normalized': '999px-normalized', offset: 1}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should normalize `*` values', () => {
|
||||
const driver = new SuperMockDriver();
|
||||
const engine = makeEngine(getBodyNode(), driver);
|
||||
|
||||
const steps = [
|
||||
style({width: '*'}),
|
||||
animate(1000, style({width: '999px'})),
|
||||
];
|
||||
|
||||
const player = invokeAnimation(engine, element, steps) as MockAnimationPlayer;
|
||||
expect(player.keyframes).toEqual([{width: '*star*', offset: 0}, {width: '999px', offset: 1}]);
|
||||
});
|
||||
beforeEach(() => {
|
||||
MockAnimationDriver.log = [];
|
||||
element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
});
|
||||
|
||||
afterEach(() => document.body.removeChild(element));
|
||||
|
||||
it('should animate a timeline', () => {
|
||||
const engine = makeEngine(getBodyNode());
|
||||
const steps = [style({height: 100}), animate(1000, style({height: 0}))];
|
||||
expect(MockAnimationDriver.log.length).toEqual(0);
|
||||
invokeAnimation(engine, element, steps);
|
||||
expect(MockAnimationDriver.log.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should not destroy timeline-based animations after they have finished', () => {
|
||||
const engine = makeEngine(getBodyNode());
|
||||
|
||||
const log: string[] = [];
|
||||
function capture(value: string) {
|
||||
return () => {
|
||||
log.push(value);
|
||||
};
|
||||
}
|
||||
|
||||
const steps = [style({height: 0}), animate(1000, style({height: 500}))];
|
||||
|
||||
const player = invokeAnimation(engine, element, steps);
|
||||
player.onDone(capture('done'));
|
||||
player.onDestroy(capture('destroy'));
|
||||
expect(log).toEqual([]);
|
||||
|
||||
player.finish();
|
||||
expect(log).toEqual(['done']);
|
||||
|
||||
player.destroy();
|
||||
expect(log).toEqual(['done', 'destroy']);
|
||||
});
|
||||
|
||||
it('should normalize the style values that are animateTransitioned within an a timeline animation',
|
||||
() => {
|
||||
const engine = makeEngine(getBodyNode(), defaultDriver, new SuffixNormalizer('-normalized'));
|
||||
|
||||
const steps = [
|
||||
style({width: '333px'}),
|
||||
animate(1000, style({width: '999px'})),
|
||||
];
|
||||
|
||||
const player = invokeAnimation(engine, element, steps) as MockAnimationPlayer;
|
||||
expect(player.keyframes).toEqual([
|
||||
{'width-normalized': '333px-normalized', offset: 0},
|
||||
{'width-normalized': '999px-normalized', offset: 1}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should normalize `*` values', () => {
|
||||
const driver = new SuperMockDriver();
|
||||
const engine = makeEngine(getBodyNode(), driver);
|
||||
|
||||
const steps = [
|
||||
style({width: '*'}),
|
||||
animate(1000, style({width: '999px'})),
|
||||
];
|
||||
|
||||
const player = invokeAnimation(engine, element, steps) as MockAnimationPlayer;
|
||||
expect(player.keyframes).toEqual([{width: '*star*', offset: 0}, {width: '999px', offset: 1}]);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
function invokeAnimation(
|
||||
engine: TimelineAnimationEngine, element: any, steps: AnimationMetadata | AnimationMetadata[],
|
||||
engine: TimelineAnimationEngine, element: any, steps: AnimationMetadata|AnimationMetadata[],
|
||||
id: string = 'id') {
|
||||
engine.register(id, steps);
|
||||
return engine.create(id, element);
|
||||
}
|
||||
|
||||
class SuffixNormalizer extends AnimationStyleNormalizer {
|
||||
constructor(private _suffix: string) { super(); }
|
||||
constructor(private _suffix: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
normalizePropertyName(propertyName: string, errors: string[]): string {
|
||||
return propertyName + this._suffix;
|
||||
@ -119,5 +122,7 @@ class SuffixNormalizer extends AnimationStyleNormalizer {
|
||||
}
|
||||
|
||||
class SuperMockDriver extends MockAnimationDriver {
|
||||
computeStyle(element: any, prop: string, defaultValue?: string): string { return '*star*'; }
|
||||
computeStyle(element: any, prop: string, defaultValue?: string): string {
|
||||
return '*star*';
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,9 @@ import {WebAnimationsPlayer} from '../../../src/render/web_animations/web_animat
|
||||
let innerPlayer: MockDomAnimation|null = null;
|
||||
beforeEach(() => {
|
||||
element = {};
|
||||
element['animate'] = () => { return innerPlayer = new MockDomAnimation(); };
|
||||
element['animate'] = () => {
|
||||
return innerPlayer = new MockDomAnimation();
|
||||
};
|
||||
});
|
||||
|
||||
describe('WebAnimationsPlayer tests', () => {
|
||||
@ -26,7 +28,7 @@ import {WebAnimationsPlayer} from '../../../src/render/web_animations/web_animat
|
||||
const player = new WebAnimationsPlayer(element, keyframes, {duration: 1000});
|
||||
|
||||
player.init();
|
||||
const p = innerPlayer !;
|
||||
const p = innerPlayer!;
|
||||
expect(p.log).toEqual(['pause']);
|
||||
|
||||
player.play();
|
||||
@ -42,7 +44,7 @@ import {WebAnimationsPlayer} from '../../../src/render/web_animations/web_animat
|
||||
const player = new WebAnimationsPlayer(element, keyframes, {duration: 1000});
|
||||
|
||||
player.play();
|
||||
const p = innerPlayer !;
|
||||
const p = innerPlayer!;
|
||||
expect(p.log).toEqual(['play']);
|
||||
});
|
||||
|
||||
@ -70,10 +72,18 @@ import {WebAnimationsPlayer} from '../../../src/render/web_animations/web_animat
|
||||
|
||||
class MockDomAnimation implements DOMAnimation {
|
||||
log: string[] = [];
|
||||
cancel(): void { this.log.push('cancel'); }
|
||||
play(): void { this.log.push('play'); }
|
||||
pause(): void { this.log.push('pause'); }
|
||||
finish(): void { this.log.push('finish'); }
|
||||
cancel(): void {
|
||||
this.log.push('cancel');
|
||||
}
|
||||
play(): void {
|
||||
this.log.push('play');
|
||||
}
|
||||
pause(): void {
|
||||
this.log.push('pause');
|
||||
}
|
||||
finish(): void {
|
||||
this.log.push('finish');
|
||||
}
|
||||
onfinish: Function = () => {};
|
||||
position: number = 0;
|
||||
currentTime: number = 0;
|
||||
|
@ -21,8 +21,8 @@ export function makeTrigger(
|
||||
const triggerAst = buildAnimationAst(driver, triggerData, errors) as TriggerAst;
|
||||
if (!skipErrors && errors.length) {
|
||||
const LINE_START = '\n - ';
|
||||
throw new Error(
|
||||
`Animation parsing for the ${name} trigger have failed:${LINE_START}${errors.join(LINE_START)}`);
|
||||
throw new Error(`Animation parsing for the ${name} trigger have failed:${LINE_START}${
|
||||
errors.join(LINE_START)}`);
|
||||
}
|
||||
return buildTrigger(name, triggerAst);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AUTO_STYLE, AnimationPlayer, NoopAnimationPlayer, ɵStyleData} from '@angular/animations';
|
||||
import {AnimationPlayer, AUTO_STYLE, NoopAnimationPlayer, ɵStyleData} from '@angular/animations';
|
||||
import {AnimationDriver, ɵallowPreviousPlayerStylesMerge as allowPreviousPlayerStylesMerge, ɵcontainsElement as containsElement, ɵinvokeQuery as invokeQuery, ɵmatchesElement as matchesElement, ɵvalidateStyleProperty as validateStyleProperty} from '@angular/animations/browser';
|
||||
|
||||
|
||||
@ -15,13 +15,17 @@ import {AnimationDriver, ɵallowPreviousPlayerStylesMerge as allowPreviousPlayer
|
||||
export class MockAnimationDriver implements AnimationDriver {
|
||||
static log: AnimationPlayer[] = [];
|
||||
|
||||
validateStyleProperty(prop: string): boolean { return validateStyleProperty(prop); }
|
||||
validateStyleProperty(prop: string): boolean {
|
||||
return validateStyleProperty(prop);
|
||||
}
|
||||
|
||||
matchesElement(element: any, selector: string): boolean {
|
||||
return matchesElement(element, selector);
|
||||
}
|
||||
|
||||
containsElement(elm1: any, elm2: any): boolean { return containsElement(elm1, elm2); }
|
||||
containsElement(elm1: any, elm2: any): boolean {
|
||||
return containsElement(elm1, elm2);
|
||||
}
|
||||
|
||||
query(element: any, selector: string, multi: boolean): any[] {
|
||||
return invokeQuery(element, selector, multi);
|
||||
@ -32,7 +36,7 @@ export class MockAnimationDriver implements AnimationDriver {
|
||||
}
|
||||
|
||||
animate(
|
||||
element: any, keyframes: {[key: string]: string | number}[], duration: number, delay: number,
|
||||
element: any, keyframes: {[key: string]: string|number}[], duration: number, delay: number,
|
||||
easing: string, previousPlayers: any[] = []): MockAnimationPlayer {
|
||||
const player =
|
||||
new MockAnimationPlayer(element, keyframes, duration, delay, easing, previousPlayers);
|
||||
@ -47,12 +51,12 @@ export class MockAnimationDriver implements AnimationDriver {
|
||||
export class MockAnimationPlayer extends NoopAnimationPlayer {
|
||||
private __finished = false;
|
||||
private __started = false;
|
||||
public previousStyles: {[key: string]: string | number} = {};
|
||||
public previousStyles: {[key: string]: string|number} = {};
|
||||
private _onInitFns: (() => any)[] = [];
|
||||
public currentSnapshot: ɵStyleData = {};
|
||||
|
||||
constructor(
|
||||
public element: any, public keyframes: {[key: string]: string | number}[],
|
||||
public element: any, public keyframes: {[key: string]: string|number}[],
|
||||
public duration: number, public delay: number, public easing: string,
|
||||
public previousPlayers: any[]) {
|
||||
super(duration, delay);
|
||||
@ -68,7 +72,9 @@ export class MockAnimationPlayer extends NoopAnimationPlayer {
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
onInit(fn: () => any) { this._onInitFns.push(fn); }
|
||||
onInit(fn: () => any) {
|
||||
this._onInitFns.push(fn);
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
init() {
|
||||
@ -95,7 +101,9 @@ export class MockAnimationPlayer extends NoopAnimationPlayer {
|
||||
this.__started = true;
|
||||
}
|
||||
|
||||
hasStarted() { return this.__started; }
|
||||
hasStarted() {
|
||||
return this.__started;
|
||||
}
|
||||
|
||||
beforeDestroy() {
|
||||
const captures: ɵStyleData = {};
|
||||
|
@ -9,7 +9,9 @@
|
||||
/**
|
||||
* Represents a set of CSS styles for use in an animation style.
|
||||
*/
|
||||
export interface ɵStyleData { [key: string]: string|number; }
|
||||
export interface ɵStyleData {
|
||||
[key: string]: string|number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents animation-step timing parameters for an animation step.
|
||||
@ -67,10 +69,10 @@ export declare interface AnimationOptions {
|
||||
*/
|
||||
delay?: number|string;
|
||||
/**
|
||||
* A set of developer-defined parameters that modify styling and timing
|
||||
* when an animation action starts. An array of key-value pairs, where the provided value
|
||||
* is used as a default.
|
||||
*/
|
||||
* A set of developer-defined parameters that modify styling and timing
|
||||
* when an animation action starts. An array of key-value pairs, where the provided value
|
||||
* is used as a default.
|
||||
*/
|
||||
params?: {[name: string]: any};
|
||||
}
|
||||
|
||||
@ -81,7 +83,9 @@ export declare interface AnimationOptions {
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export declare interface AnimateChildOptions extends AnimationOptions { duration?: number|string; }
|
||||
export declare interface AnimateChildOptions extends AnimationOptions {
|
||||
duration?: number|string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Constants for the categories of parameters that can be defined for animations.
|
||||
@ -171,7 +175,9 @@ export const AUTO_STYLE = '*';
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface AnimationMetadata { type: AnimationMetadataType; }
|
||||
export interface AnimationMetadata {
|
||||
type: AnimationMetadataType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains an animation trigger. Instantiated and returned by the
|
||||
@ -181,8 +187,8 @@ export interface AnimationMetadata { type: AnimationMetadataType; }
|
||||
*/
|
||||
export interface AnimationTriggerMetadata extends AnimationMetadata {
|
||||
/**
|
||||
* The trigger name, used to associate it with an element. Unique within the component.
|
||||
*/
|
||||
* The trigger name, used to associate it with an element. Unique within the component.
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* An animation definition object, containing an array of state and transition declarations.
|
||||
@ -654,8 +660,9 @@ export function trigger(name: string, definitions: AnimationMetadata[]): Animati
|
||||
* @publicApi
|
||||
*/
|
||||
export function animate(
|
||||
timings: string | number, styles: AnimationStyleMetadata | AnimationKeyframesSequenceMetadata |
|
||||
null = null): AnimationAnimateMetadata {
|
||||
timings: string|number,
|
||||
styles: AnimationStyleMetadata|AnimationKeyframesSequenceMetadata|null =
|
||||
null): AnimationAnimateMetadata {
|
||||
return {type: AnimationMetadataType.Animate, styles, timings};
|
||||
}
|
||||
|
||||
@ -693,7 +700,7 @@ export function animate(
|
||||
* @publicApi
|
||||
*/
|
||||
export function group(
|
||||
steps: AnimationMetadata[], options: AnimationOptions | null = null): AnimationGroupMetadata {
|
||||
steps: AnimationMetadata[], options: AnimationOptions|null = null): AnimationGroupMetadata {
|
||||
return {type: AnimationMetadataType.Group, steps, options};
|
||||
}
|
||||
|
||||
@ -721,7 +728,8 @@ export function group(
|
||||
* @usageNotes
|
||||
* When you pass an array of steps to a
|
||||
* `transition()` call, the steps run sequentially by default.
|
||||
* Compare this to the `{@link animations/group group()}` call, which runs animation steps in parallel.
|
||||
* Compare this to the `{@link animations/group group()}` call, which runs animation steps in
|
||||
*parallel.
|
||||
*
|
||||
* When a sequence is used within a `{@link animations/group group()}` or a `transition()` call,
|
||||
* execution continues to the next instruction only after each of the inner animation
|
||||
@ -729,8 +737,8 @@ export function group(
|
||||
*
|
||||
* @publicApi
|
||||
**/
|
||||
export function sequence(steps: AnimationMetadata[], options: AnimationOptions | null = null):
|
||||
AnimationSequenceMetadata {
|
||||
export function sequence(
|
||||
steps: AnimationMetadata[], options: AnimationOptions|null = null): AnimationSequenceMetadata {
|
||||
return {type: AnimationMetadataType.Sequence, steps, options};
|
||||
}
|
||||
|
||||
@ -773,9 +781,8 @@ export function sequence(steps: AnimationMetadata[], options: AnimationOptions |
|
||||
*
|
||||
* @publicApi
|
||||
**/
|
||||
export function style(
|
||||
tokens: '*' | {[key: string]: string | number} |
|
||||
Array<'*'|{[key: string]: string | number}>): AnimationStyleMetadata {
|
||||
export function style(tokens: '*'|{[key: string]: string | number}|
|
||||
Array<'*'|{[key: string]: string | number}>): AnimationStyleMetadata {
|
||||
return {type: AnimationMetadataType.Style, styles: tokens, offset: null};
|
||||
}
|
||||
|
||||
@ -1032,10 +1039,10 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
|
||||
* @publicApi
|
||||
**/
|
||||
export function transition(
|
||||
stateChangeExpr: string | ((fromState: string, toState: string, element?: any,
|
||||
params?: {[key: string]: any}) => boolean),
|
||||
steps: AnimationMetadata | AnimationMetadata[],
|
||||
options: AnimationOptions | null = null): AnimationTransitionMetadata {
|
||||
stateChangeExpr: string|
|
||||
((fromState: string, toState: string, element?: any, params?: {[key: string]: any}) => boolean),
|
||||
steps: AnimationMetadata|AnimationMetadata[],
|
||||
options: AnimationOptions|null = null): AnimationTransitionMetadata {
|
||||
return {type: AnimationMetadataType.Transition, expr: stateChangeExpr, animation: steps, options};
|
||||
}
|
||||
|
||||
@ -1085,8 +1092,8 @@ export function transition(
|
||||
* @publicApi
|
||||
*/
|
||||
export function animation(
|
||||
steps: AnimationMetadata | AnimationMetadata[],
|
||||
options: AnimationOptions | null = null): AnimationReferenceMetadata {
|
||||
steps: AnimationMetadata|AnimationMetadata[],
|
||||
options: AnimationOptions|null = null): AnimationReferenceMetadata {
|
||||
return {type: AnimationMetadataType.Reference, animation: steps, options};
|
||||
}
|
||||
|
||||
@ -1109,7 +1116,7 @@ export function animation(
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function animateChild(options: AnimateChildOptions | null = null):
|
||||
export function animateChild(options: AnimateChildOptions|null = null):
|
||||
AnimationAnimateChildMetadata {
|
||||
return {type: AnimationMetadataType.AnimateChild, options};
|
||||
}
|
||||
@ -1126,7 +1133,7 @@ export function animateChild(options: AnimateChildOptions | null = null):
|
||||
*/
|
||||
export function useAnimation(
|
||||
animation: AnimationReferenceMetadata,
|
||||
options: AnimationOptions | null = null): AnimationAnimateRefMetadata {
|
||||
options: AnimationOptions|null = null): AnimationAnimateRefMetadata {
|
||||
return {type: AnimationMetadataType.AnimateRef, animation, options};
|
||||
}
|
||||
|
||||
@ -1179,7 +1186,7 @@ export function useAnimation(
|
||||
* ### Usage Example
|
||||
*
|
||||
* The following example queries for inner elements and animates them
|
||||
* individually using `animate()`.
|
||||
* individually using `animate()`.
|
||||
*
|
||||
* ```typescript
|
||||
* @Component({
|
||||
@ -1218,8 +1225,8 @@ export function useAnimation(
|
||||
* @publicApi
|
||||
*/
|
||||
export function query(
|
||||
selector: string, animation: AnimationMetadata | AnimationMetadata[],
|
||||
options: AnimationQueryOptions | null = null): AnimationQueryMetadata {
|
||||
selector: string, animation: AnimationMetadata|AnimationMetadata[],
|
||||
options: AnimationQueryOptions|null = null): AnimationQueryMetadata {
|
||||
return {type: AnimationMetadataType.Query, selector, animation, options};
|
||||
}
|
||||
|
||||
@ -1303,8 +1310,7 @@ export function query(
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export function stagger(
|
||||
timings: string | number,
|
||||
animation: AnimationMetadata | AnimationMetadata[]): AnimationStaggerMetadata {
|
||||
export function stagger(timings: string|number, animation: AnimationMetadata|AnimationMetadata[]):
|
||||
AnimationStaggerMetadata {
|
||||
return {type: AnimationMetadataType.Stagger, timings, animation};
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
*/
|
||||
export {AnimationBuilder, AnimationFactory} from './animation_builder';
|
||||
export {AnimationEvent} from './animation_event';
|
||||
export {AUTO_STYLE, AnimateChildOptions, AnimateTimings, AnimationAnimateChildMetadata, AnimationAnimateMetadata, AnimationAnimateRefMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationMetadataType, AnimationOptions, AnimationQueryMetadata, AnimationQueryOptions, AnimationReferenceMetadata, AnimationSequenceMetadata, AnimationStaggerMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata, AnimationTriggerMetadata, animate, animateChild, animation, group, keyframes, query, sequence, stagger, state, style, transition, trigger, useAnimation, ɵStyleData} from './animation_metadata';
|
||||
export {animate, animateChild, AnimateChildOptions, AnimateTimings, animation, AnimationAnimateChildMetadata, AnimationAnimateMetadata, AnimationAnimateRefMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationMetadataType, AnimationOptions, AnimationQueryMetadata, AnimationQueryOptions, AnimationReferenceMetadata, AnimationSequenceMetadata, AnimationStaggerMetadata, AnimationStateMetadata, AnimationStyleMetadata, AnimationTransitionMetadata, AnimationTriggerMetadata, AUTO_STYLE, group, keyframes, query, sequence, stagger, state, style, transition, trigger, useAnimation, ɵStyleData} from './animation_metadata';
|
||||
export {AnimationPlayer, NoopAnimationPlayer} from './players/animation_player';
|
||||
|
||||
export * from './private_export';
|
||||
|
@ -69,9 +69,13 @@ export class AnimationGroupPlayer implements AnimationPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
init(): void { this.players.forEach(player => player.init()); }
|
||||
init(): void {
|
||||
this.players.forEach(player => player.init());
|
||||
}
|
||||
|
||||
onStart(fn: () => void): void { this._onStartFns.push(fn); }
|
||||
onStart(fn: () => void): void {
|
||||
this._onStartFns.push(fn);
|
||||
}
|
||||
|
||||
private _onStart() {
|
||||
if (!this.hasStarted()) {
|
||||
@ -81,11 +85,17 @@ export class AnimationGroupPlayer implements AnimationPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
onDone(fn: () => void): void { this._onDoneFns.push(fn); }
|
||||
onDone(fn: () => void): void {
|
||||
this._onDoneFns.push(fn);
|
||||
}
|
||||
|
||||
onDestroy(fn: () => void): void { this._onDestroyFns.push(fn); }
|
||||
onDestroy(fn: () => void): void {
|
||||
this._onDestroyFns.push(fn);
|
||||
}
|
||||
|
||||
hasStarted() { return this._started; }
|
||||
hasStarted() {
|
||||
return this._started;
|
||||
}
|
||||
|
||||
play() {
|
||||
if (!this.parentPlayer) {
|
||||
@ -95,16 +105,22 @@ export class AnimationGroupPlayer implements AnimationPlayer {
|
||||
this.players.forEach(player => player.play());
|
||||
}
|
||||
|
||||
pause(): void { this.players.forEach(player => player.pause()); }
|
||||
pause(): void {
|
||||
this.players.forEach(player => player.pause());
|
||||
}
|
||||
|
||||
restart(): void { this.players.forEach(player => player.restart()); }
|
||||
restart(): void {
|
||||
this.players.forEach(player => player.restart());
|
||||
}
|
||||
|
||||
finish(): void {
|
||||
this._onFinish();
|
||||
this.players.forEach(player => player.finish());
|
||||
}
|
||||
|
||||
destroy(): void { this._onDestroy(); }
|
||||
destroy(): void {
|
||||
this._onDestroy();
|
||||
}
|
||||
|
||||
private _onDestroy() {
|
||||
if (!this._destroyed) {
|
||||
|
@ -94,11 +94,13 @@ export interface AnimationPlayer {
|
||||
* Provides a callback to invoke before the animation is destroyed.
|
||||
*/
|
||||
beforeDestroy?: () => any;
|
||||
/** @internal
|
||||
/**
|
||||
* @internal
|
||||
* Internal
|
||||
*/
|
||||
triggerCallback?: (phaseName: string) => void;
|
||||
/** @internal
|
||||
/**
|
||||
* @internal
|
||||
* Internal
|
||||
*/
|
||||
disabled?: boolean;
|
||||
@ -124,7 +126,9 @@ export class NoopAnimationPlayer implements AnimationPlayer {
|
||||
private _finished = false;
|
||||
public parentPlayer: AnimationPlayer|null = null;
|
||||
public readonly totalTime: number;
|
||||
constructor(duration: number = 0, delay: number = 0) { this.totalTime = duration + delay; }
|
||||
constructor(duration: number = 0, delay: number = 0) {
|
||||
this.totalTime = duration + delay;
|
||||
}
|
||||
private _onFinish() {
|
||||
if (!this._finished) {
|
||||
this._finished = true;
|
||||
@ -132,10 +136,18 @@ export class NoopAnimationPlayer implements AnimationPlayer {
|
||||
this._onDoneFns = [];
|
||||
}
|
||||
}
|
||||
onStart(fn: () => void): void { this._onStartFns.push(fn); }
|
||||
onDone(fn: () => void): void { this._onDoneFns.push(fn); }
|
||||
onDestroy(fn: () => void): void { this._onDestroyFns.push(fn); }
|
||||
hasStarted(): boolean { return this._started; }
|
||||
onStart(fn: () => void): void {
|
||||
this._onStartFns.push(fn);
|
||||
}
|
||||
onDone(fn: () => void): void {
|
||||
this._onDoneFns.push(fn);
|
||||
}
|
||||
onDestroy(fn: () => void): void {
|
||||
this._onDestroyFns.push(fn);
|
||||
}
|
||||
hasStarted(): boolean {
|
||||
return this._started;
|
||||
}
|
||||
init(): void {}
|
||||
play(): void {
|
||||
if (!this.hasStarted()) {
|
||||
@ -146,7 +158,9 @@ export class NoopAnimationPlayer implements AnimationPlayer {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
triggerMicrotask() { scheduleMicroTask(() => this._onFinish()); }
|
||||
triggerMicrotask() {
|
||||
scheduleMicroTask(() => this._onFinish());
|
||||
}
|
||||
|
||||
private _onStart() {
|
||||
this._onStartFns.forEach(fn => fn());
|
||||
@ -155,7 +169,9 @@ export class NoopAnimationPlayer implements AnimationPlayer {
|
||||
|
||||
pause(): void {}
|
||||
restart(): void {}
|
||||
finish(): void { this._onFinish(); }
|
||||
finish(): void {
|
||||
this._onFinish();
|
||||
}
|
||||
destroy(): void {
|
||||
if (!this._destroyed) {
|
||||
this._destroyed = true;
|
||||
@ -169,7 +185,9 @@ export class NoopAnimationPlayer implements AnimationPlayer {
|
||||
}
|
||||
reset(): void {}
|
||||
setPosition(position: number): void {}
|
||||
getPosition(): number { return 0; }
|
||||
getPosition(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
triggerCallback(phaseName: string): void {
|
||||
|
@ -69,7 +69,9 @@ import {scheduleMicroTask} from '../src/util';
|
||||
const log: string[] = [];
|
||||
|
||||
const player = new NoopAnimationPlayer();
|
||||
player.onStart(() => { scheduleMicroTask(() => log.push('started')); });
|
||||
player.onStart(() => {
|
||||
scheduleMicroTask(() => log.push('started'));
|
||||
});
|
||||
player.onDone(() => log.push('done'));
|
||||
expect(log).toEqual([]);
|
||||
|
||||
|
@ -38,7 +38,7 @@ export function runMain(
|
||||
// API extractor doesn't always support the version of TypeScript used in the repo
|
||||
// example: at the moment it is not compatable with 3.2
|
||||
// to use the internal TypeScript we shall not create a program but rather pass a parsed tsConfig.
|
||||
const parsedTsConfig = parsedConfig !.config as any;
|
||||
const parsedTsConfig = parsedConfig!.config as any;
|
||||
const compilerOptions = parsedTsConfig.compilerOptions;
|
||||
for (const [key, values] of Object.entries<string[]>(compilerOptions.paths)) {
|
||||
if (key === '*') {
|
||||
@ -113,8 +113,8 @@ api-extractor: running with
|
||||
const dtsBundleOuts = dtsBundleOut.split(',');
|
||||
|
||||
if (entryPoints.length !== entryPoints.length) {
|
||||
throw new Error(
|
||||
`Entry points count (${entryPoints.length}) does not match Bundle out count (${dtsBundleOuts.length})`);
|
||||
throw new Error(`Entry points count (${entryPoints.length}) does not match Bundle out count (${
|
||||
dtsBundleOuts.length})`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < entryPoints.length; i++) {
|
||||
|
@ -9,12 +9,12 @@
|
||||
/// <reference types='node'/>
|
||||
|
||||
import {spawn} from 'child_process';
|
||||
import {copyFileSync, existsSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync} from 'fs';
|
||||
import {copyFileSync, existsSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync} from 'fs';
|
||||
import {platform} from 'os';
|
||||
import {dirname, join, normalize} from 'path';
|
||||
|
||||
export type Executable = 'bazel' | 'ibazel';
|
||||
export type Command = 'build' | 'test' | 'run' | 'coverage' | 'query';
|
||||
export type Executable = 'bazel'|'ibazel';
|
||||
export type Command = 'build'|'test'|'run'|'coverage'|'query';
|
||||
|
||||
/**
|
||||
* Spawn the Bazel process. Trap SINGINT to make sure Bazel process is killed.
|
||||
|
@ -13,27 +13,29 @@ import {JsonObject} from '@angular-devkit/core';
|
||||
import {checkInstallation, copyBazelFiles, deleteBazelFiles, getTemplateDir, runBazel} from './bazel';
|
||||
import {Schema} from './schema';
|
||||
|
||||
async function _bazelBuilder(options: JsonObject & Schema, context: BuilderContext, ):
|
||||
Promise<BuilderOutput> {
|
||||
const {logger, workspaceRoot} = context;
|
||||
const {bazelCommand, leaveBazelFilesOnDisk, targetLabel, watch} = options;
|
||||
const executable = watch ? 'ibazel' : 'bazel';
|
||||
const binary = checkInstallation(executable, workspaceRoot);
|
||||
const templateDir = getTemplateDir(workspaceRoot);
|
||||
const bazelFiles = copyBazelFiles(workspaceRoot, templateDir);
|
||||
async function _bazelBuilder(
|
||||
options: JsonObject&Schema,
|
||||
context: BuilderContext,
|
||||
): Promise<BuilderOutput> {
|
||||
const {logger, workspaceRoot} = context;
|
||||
const {bazelCommand, leaveBazelFilesOnDisk, targetLabel, watch} = options;
|
||||
const executable = watch ? 'ibazel' : 'bazel';
|
||||
const binary = checkInstallation(executable, workspaceRoot);
|
||||
const templateDir = getTemplateDir(workspaceRoot);
|
||||
const bazelFiles = copyBazelFiles(workspaceRoot, templateDir);
|
||||
|
||||
try {
|
||||
const flags: string[] = [];
|
||||
await runBazel(workspaceRoot, binary, bazelCommand, targetLabel, flags);
|
||||
return {success: true};
|
||||
} catch (err) {
|
||||
logger.error(err.message);
|
||||
return {success: false};
|
||||
} finally {
|
||||
if (!leaveBazelFilesOnDisk) {
|
||||
deleteBazelFiles(bazelFiles); // this will never throw
|
||||
}
|
||||
}
|
||||
try {
|
||||
const flags: string[] = [];
|
||||
await runBazel(workspaceRoot, binary, bazelCommand, targetLabel, flags);
|
||||
return {success: true};
|
||||
} catch (err) {
|
||||
logger.error(err.message);
|
||||
return {success: false};
|
||||
} finally {
|
||||
if (!leaveBazelFilesOnDisk) {
|
||||
deleteBazelFiles(bazelFiles); // this will never throw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default createBuilder(_bazelBuilder);
|
||||
|
@ -22,7 +22,9 @@ function main(args: string[]): number {
|
||||
const paramFilePath = args[0];
|
||||
|
||||
// Bazel params may be surrounded with quotes
|
||||
function unquoteParameter(s: string) { return s.replace(/^'(.*)'$/, '$1'); }
|
||||
function unquoteParameter(s: string) {
|
||||
return s.replace(/^'(.*)'$/, '$1');
|
||||
}
|
||||
|
||||
// Parameters are specified in the file one per line.
|
||||
const params = fs.readFileSync(paramFilePath, 'utf-8').split('\n').map(unquoteParameter);
|
||||
@ -109,7 +111,7 @@ function main(args: string[]): number {
|
||||
* @param inputPath Path to the file in the input tree.
|
||||
* @param fileContent Content of the file.
|
||||
*/
|
||||
function writeFileFromInputPath(inputPath: string, fileContent: string | Buffer) {
|
||||
function writeFileFromInputPath(inputPath: string, fileContent: string|Buffer) {
|
||||
// We want the relative path from the given file to its ancestor "root" directory.
|
||||
// This root depends on whether the file lives in the source tree (srcDir) as a basic file
|
||||
// input to ng_package, the bin output tree (binDir) as the output of another rule, or
|
||||
@ -164,9 +166,15 @@ function main(args: string[]): number {
|
||||
esm2015.forEach(file => writeEsmFile(file, '', 'esm2015'));
|
||||
esm5.forEach(file => writeEsmFile(file, '.esm5', 'esm5'));
|
||||
|
||||
bundles.forEach(bundle => { copyFile(bundle, out, 'bundles'); });
|
||||
fesm2015.forEach(file => { copyFile(file, out, 'fesm2015'); });
|
||||
fesm5.forEach(file => { copyFile(file, out, 'fesm5'); });
|
||||
bundles.forEach(bundle => {
|
||||
copyFile(bundle, out, 'bundles');
|
||||
});
|
||||
fesm2015.forEach(file => {
|
||||
copyFile(file, out, 'fesm2015');
|
||||
});
|
||||
fesm5.forEach(file => {
|
||||
copyFile(file, out, 'fesm5');
|
||||
});
|
||||
|
||||
// Copy all type definitions into the package. This is necessary so that developers can use
|
||||
// the package with type definitions.
|
||||
@ -419,14 +427,16 @@ export * from '${srcDirRelative(inputPath, typingsFile.replace(/\.d\.tsx?$/, '')
|
||||
* Normalizes the specified path by replacing backslash separators with Posix
|
||||
* forward slash separators.
|
||||
*/
|
||||
function normalizeSeparators(path: string): string { return path.replace(/\\/g, '/'); }
|
||||
function normalizeSeparators(path: string): string {
|
||||
return path.replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewires metadata to point to the flattened dts file.
|
||||
*
|
||||
* @param metadataPath the metadata file path
|
||||
* @param typingsPath the typings bundle entrypoint
|
||||
*/
|
||||
* Rewires metadata to point to the flattened dts file.
|
||||
*
|
||||
* @param metadataPath the metadata file path
|
||||
* @param typingsPath the typings bundle entrypoint
|
||||
*/
|
||||
function rewireMetadata(metadataPath: string, typingsPath: string): string {
|
||||
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
|
||||
|
||||
@ -470,7 +480,7 @@ export function newArray<T>(size: number, value: T): T[];
|
||||
export function newArray<T>(size: number, value?: T): T[] {
|
||||
const list: T[] = [];
|
||||
for (let i = 0; i < size; i++) {
|
||||
list.push(value !);
|
||||
list.push(value!);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import * as ng from '@angular/compiler-cli';
|
||||
import {BazelOptions, CachedFileLoader, CompilerHost, FileCache, FileLoader, UncachedFileLoader, constructManifest, debug, parseTsconfig, resolveNormalizedPath, runAsWorker, runWorkerLoop} from '@bazel/typescript';
|
||||
import {BazelOptions, CachedFileLoader, CompilerHost, constructManifest, debug, FileCache, FileLoader, parseTsconfig, resolveNormalizedPath, runAsWorker, runWorkerLoop, UncachedFileLoader} from '@bazel/typescript';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as tsickle from 'tsickle';
|
||||
@ -123,7 +123,12 @@ export function runOneBuild(args: string[], inputs?: {[path: string]: string}):
|
||||
const {diagnostics} = compile({
|
||||
allDepsCompiledWithBazel: ALL_DEPS_COMPILED_WITH_BAZEL,
|
||||
useManifestPathsAsModuleName: _useManifestPathsAsModuleName,
|
||||
expectedOuts: expectedOut, compilerOpts, tsHost, bazelOpts, files, inputs,
|
||||
expectedOuts: expectedOut,
|
||||
compilerOpts,
|
||||
tsHost,
|
||||
bazelOpts,
|
||||
files,
|
||||
inputs,
|
||||
});
|
||||
if (diagnostics.length) {
|
||||
console.error(ng.formatDiagnostics(diagnostics));
|
||||
@ -142,16 +147,24 @@ export function relativeToRootDirs(filePath: string, rootDirs: string[]): string
|
||||
return filePath;
|
||||
}
|
||||
|
||||
export function compile({allDepsCompiledWithBazel = true, useManifestPathsAsModuleName,
|
||||
compilerOpts, tsHost, bazelOpts, files, inputs, expectedOuts,
|
||||
gatherDiagnostics, bazelHost}: {
|
||||
export function compile({
|
||||
allDepsCompiledWithBazel = true,
|
||||
useManifestPathsAsModuleName,
|
||||
compilerOpts,
|
||||
tsHost,
|
||||
bazelOpts,
|
||||
files,
|
||||
inputs,
|
||||
expectedOuts,
|
||||
gatherDiagnostics,
|
||||
bazelHost
|
||||
}: {
|
||||
allDepsCompiledWithBazel?: boolean,
|
||||
useManifestPathsAsModuleName?: boolean,
|
||||
compilerOpts: ng.CompilerOptions,
|
||||
tsHost: ts.CompilerHost, inputs?: {[path: string]: string},
|
||||
bazelOpts: BazelOptions,
|
||||
files: string[],
|
||||
expectedOuts: string[],
|
||||
useManifestPathsAsModuleName?: boolean, compilerOpts: ng.CompilerOptions, tsHost: ts.CompilerHost,
|
||||
inputs?: {[path: string]: string},
|
||||
bazelOpts: BazelOptions,
|
||||
files: string[],
|
||||
expectedOuts: string[],
|
||||
gatherDiagnostics?: (program: ng.Program) => ng.Diagnostics,
|
||||
bazelHost?: CompilerHost,
|
||||
}): {diagnostics: ng.Diagnostics, program: ng.Program} {
|
||||
@ -362,7 +375,7 @@ export function compile({allDepsCompiledWithBazel = true, useManifestPathsAsModu
|
||||
|
||||
if ((compilerOpts.module === ts.ModuleKind.UMD || compilerOpts.module === ts.ModuleKind.AMD) &&
|
||||
ngHost.amdModuleName) {
|
||||
return ngHost.amdModuleName({ fileName: importedFilePath } as ts.SourceFile);
|
||||
return ngHost.amdModuleName({fileName: importedFilePath} as ts.SourceFile);
|
||||
}
|
||||
|
||||
// If no AMD module name has been set for the source file by the `@bazel/typescript` compiler
|
||||
@ -434,8 +447,10 @@ export function compile({allDepsCompiledWithBazel = true, useManifestPathsAsModu
|
||||
const {diagnostics, emitResult, program} = ng.performCompilation({
|
||||
rootNames: files,
|
||||
options: compilerOpts,
|
||||
host: ngHost, emitCallback,
|
||||
mergeEmitResultsCallback: tsickle.mergeEmitResults, gatherDiagnostics
|
||||
host: ngHost,
|
||||
emitCallback,
|
||||
mergeEmitResultsCallback: tsickle.mergeEmitResults,
|
||||
gatherDiagnostics
|
||||
});
|
||||
const tsickleEmitResult = emitResult as tsickle.EmitResult;
|
||||
let externs = '/** @externs */\n';
|
||||
@ -512,9 +527,9 @@ function convertToForwardSlashPath(filePath: string): string {
|
||||
|
||||
function gatherDiagnosticsForInputsOnly(
|
||||
options: ng.CompilerOptions, bazelOpts: BazelOptions,
|
||||
ngProgram: ng.Program): (ng.Diagnostic | ts.Diagnostic)[] {
|
||||
ngProgram: ng.Program): (ng.Diagnostic|ts.Diagnostic)[] {
|
||||
const tsProgram = ngProgram.getTsProgram();
|
||||
const diagnostics: (ng.Diagnostic | ts.Diagnostic)[] = [];
|
||||
const diagnostics: (ng.Diagnostic|ts.Diagnostic)[] = [];
|
||||
// These checks mirror ts.getPreEmitDiagnostics, with the important
|
||||
// exception of avoiding b/30708240, which is that if you call
|
||||
// program.getDeclarationDiagnostics() it somehow corrupts the emit.
|
||||
|
@ -10,7 +10,6 @@ import {HostTree} from '@angular-devkit/schematics';
|
||||
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
|
||||
|
||||
describe('ng-add schematic', () => {
|
||||
|
||||
const defaultOptions = {name: 'demo'};
|
||||
let host: UnitTestTree;
|
||||
let schematicRunner: SchematicTestRunner;
|
||||
@ -61,7 +60,7 @@ describe('ng-add schematic', () => {
|
||||
new SchematicTestRunner('@angular/bazel', require.resolve('../collection.json'));
|
||||
});
|
||||
|
||||
it('throws if package.json is not found', async() => {
|
||||
it('throws if package.json is not found', async () => {
|
||||
expect(host.files).toContain('/package.json');
|
||||
host.delete('/package.json');
|
||||
|
||||
@ -76,7 +75,7 @@ describe('ng-add schematic', () => {
|
||||
expect(message).toBe('Could not read package.json.');
|
||||
});
|
||||
|
||||
it('throws if angular.json is not found', async() => {
|
||||
it('throws if angular.json is not found', async () => {
|
||||
expect(host.files).toContain('/angular.json');
|
||||
host.delete('/angular.json');
|
||||
|
||||
@ -91,7 +90,7 @@ describe('ng-add schematic', () => {
|
||||
expect(message).toBe('Could not find angular.json');
|
||||
});
|
||||
|
||||
it('should add @angular/bazel to package.json dependencies', async() => {
|
||||
it('should add @angular/bazel to package.json dependencies', async () => {
|
||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
||||
const {files} = host;
|
||||
expect(files).toContain('/package.json');
|
||||
@ -106,7 +105,7 @@ describe('ng-add schematic', () => {
|
||||
expect(Object.keys(json.devDependencies)).toContain(bazel);
|
||||
});
|
||||
|
||||
it('should add @bazel/* dev dependencies', async() => {
|
||||
it('should add @bazel/* dev dependencies', async () => {
|
||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
||||
const content = host.readContent('/package.json');
|
||||
const json = JSON.parse(content);
|
||||
@ -118,7 +117,7 @@ describe('ng-add schematic', () => {
|
||||
expect(devDeps).toContain('@bazel/typescript');
|
||||
});
|
||||
|
||||
it('should replace an existing dev dependency', async() => {
|
||||
it('should replace an existing dev dependency', async () => {
|
||||
expect(host.files).toContain('/package.json');
|
||||
const packageJson = JSON.parse(host.readContent('/package.json'));
|
||||
packageJson.devDependencies['@angular/bazel'] = '4.2.42';
|
||||
@ -126,12 +125,12 @@ describe('ng-add schematic', () => {
|
||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
||||
const content = host.readContent('/package.json');
|
||||
// It is possible that a dep gets added twice if the package already exists.
|
||||
expect(content.match(/@angular\/bazel/g) !.length).toEqual(1);
|
||||
expect(content.match(/@angular\/bazel/g)!.length).toEqual(1);
|
||||
const json = JSON.parse(content);
|
||||
expect(json.devDependencies['@angular/bazel']).toBe('1.2.3');
|
||||
});
|
||||
|
||||
it('should remove an existing dependency', async() => {
|
||||
it('should remove an existing dependency', async () => {
|
||||
expect(host.files).toContain('/package.json');
|
||||
const packageJson = JSON.parse(host.readContent('/package.json'));
|
||||
packageJson.dependencies['@angular/bazel'] = '4.2.42';
|
||||
@ -144,7 +143,7 @@ describe('ng-add schematic', () => {
|
||||
expect(json.devDependencies['@angular/bazel']).toBe('1.2.3');
|
||||
});
|
||||
|
||||
it('should remove unneeded dependencies', async() => {
|
||||
it('should remove unneeded dependencies', async () => {
|
||||
const packageJson = JSON.parse(host.readContent('/package.json'));
|
||||
packageJson.devDependencies['@angular-devkit/build-angular'] = '1.2.3';
|
||||
host.overwrite('/package.json', JSON.stringify(packageJson));
|
||||
@ -154,7 +153,7 @@ describe('ng-add schematic', () => {
|
||||
expect(json.devDependencies['angular-devkit/build-angular']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should append to scripts.postinstall if it already exists', async() => {
|
||||
it('should append to scripts.postinstall if it already exists', async () => {
|
||||
const packageJson = JSON.parse(host.readContent('/package.json'));
|
||||
packageJson['scripts'] = {
|
||||
postinstall: 'angular rocks',
|
||||
@ -167,7 +166,7 @@ describe('ng-add schematic', () => {
|
||||
.toBe('angular rocks; ngcc --properties es2015 browser module main');
|
||||
});
|
||||
|
||||
it('should update ngcc in scripts.postinstall if it already exists', async() => {
|
||||
it('should update ngcc in scripts.postinstall if it already exists', async () => {
|
||||
const packageJson = JSON.parse(host.readContent('/package.json'));
|
||||
packageJson['scripts'] = {
|
||||
postinstall:
|
||||
@ -180,14 +179,14 @@ describe('ng-add schematic', () => {
|
||||
expect(json.scripts['postinstall']).toBe('ngcc --properties es2015 browser module main');
|
||||
});
|
||||
|
||||
it('should not create Bazel workspace file', async() => {
|
||||
it('should not create Bazel workspace file', async () => {
|
||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
||||
const {files} = host;
|
||||
expect(files).not.toContain('/WORKSPACE');
|
||||
expect(files).not.toContain('/BUILD.bazel');
|
||||
});
|
||||
|
||||
it('should produce main.dev.ts and main.prod.ts for AOT', async() => {
|
||||
it('should produce main.dev.ts and main.prod.ts for AOT', async () => {
|
||||
host.create('/src/main.ts', 'generated by CLI');
|
||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
||||
const {files} = host;
|
||||
@ -199,7 +198,7 @@ describe('ng-add schematic', () => {
|
||||
expect(files).toContain('/src/main.ts');
|
||||
});
|
||||
|
||||
it('should not overwrite index.html with script tags', async() => {
|
||||
it('should not overwrite index.html with script tags', async () => {
|
||||
host.create('/src/index.html', '<html>Hello World</html>');
|
||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
||||
const {files} = host;
|
||||
@ -209,14 +208,14 @@ describe('ng-add schematic', () => {
|
||||
expect(content).not.toMatch('<script src="/bundle.min.js"></script>');
|
||||
});
|
||||
|
||||
it('should generate main.dev.ts and main.prod.ts', async() => {
|
||||
it('should generate main.dev.ts and main.prod.ts', async () => {
|
||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
||||
const {files} = host;
|
||||
expect(files).toContain('/src/main.dev.ts');
|
||||
expect(files).toContain('/src/main.prod.ts');
|
||||
});
|
||||
|
||||
it('should overwrite .gitignore for bazel-out directory', async() => {
|
||||
it('should overwrite .gitignore for bazel-out directory', async () => {
|
||||
host.create('.gitignore', '\n# compiled output\n');
|
||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
||||
const {files} = host;
|
||||
@ -225,7 +224,7 @@ describe('ng-add schematic', () => {
|
||||
expect(content).toMatch('\n# compiled output\n/bazel-out\n');
|
||||
});
|
||||
|
||||
it('should create a backup for original angular.json', async() => {
|
||||
it('should create a backup for original angular.json', async () => {
|
||||
expect(host.files).toContain('/angular.json');
|
||||
const original = host.readContent('/angular.json');
|
||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
||||
@ -235,7 +234,7 @@ describe('ng-add schematic', () => {
|
||||
expect(content).toMatch(original);
|
||||
});
|
||||
|
||||
it('should update angular.json to use Bazel builder', async() => {
|
||||
it('should update angular.json to use Bazel builder', async () => {
|
||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
||||
const {files} = host;
|
||||
expect(files).toContain('/angular.json');
|
||||
@ -256,7 +255,7 @@ describe('ng-add schematic', () => {
|
||||
expect(lint.builder).toBe('@angular-devkit/build-angular:tslint');
|
||||
});
|
||||
|
||||
it('should get defaultProject if name is not provided', async() => {
|
||||
it('should get defaultProject if name is not provided', async () => {
|
||||
const options = {};
|
||||
host = await schematicRunner.runSchematicAsync('ng-add', options, host).toPromise();
|
||||
const content = host.readContent('/angular.json');
|
||||
@ -281,7 +280,7 @@ describe('ng-add schematic', () => {
|
||||
['~7.0.1', false],
|
||||
];
|
||||
for (const [version, upgrade] of cases) {
|
||||
it(`should ${upgrade ? '' : 'not '}upgrade v${version}')`, async() => {
|
||||
it(`should ${upgrade ? '' : 'not '}upgrade v${version}')`, async () => {
|
||||
host.overwrite('package.json', JSON.stringify({
|
||||
name: 'demo',
|
||||
dependencies: {
|
||||
@ -305,7 +304,7 @@ describe('ng-add schematic', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('should add a postinstall step to package.json', async() => {
|
||||
it('should add a postinstall step to package.json', async () => {
|
||||
host = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, host).toPromise();
|
||||
expect(host.files).toContain('/package.json');
|
||||
const content = host.readContent('/package.json');
|
||||
@ -313,7 +312,7 @@ describe('ng-add schematic', () => {
|
||||
expect(json.scripts.postinstall).toBe('ngcc --properties es2015 browser module main');
|
||||
});
|
||||
|
||||
it('should work when run on a minimal project (without test and e2e targets)', async() => {
|
||||
it('should work when run on a minimal project (without test and e2e targets)', async () => {
|
||||
host.overwrite('angular.json', JSON.stringify({
|
||||
projects: {
|
||||
'demo': {
|
||||
@ -338,5 +337,4 @@ describe('ng-add schematic', () => {
|
||||
|
||||
expect(error).toBeNull();
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -8,8 +8,9 @@
|
||||
* @fileoverview Schematics for ng-new project that builds with Bazel.
|
||||
*/
|
||||
|
||||
import {Rule, Tree, chain, externalSchematic, schematic} from '@angular-devkit/schematics';
|
||||
import {chain, externalSchematic, Rule, schematic, Tree} from '@angular-devkit/schematics';
|
||||
import {validateProjectName} from '@schematics/angular/utility/validation';
|
||||
|
||||
import {Schema} from './schema';
|
||||
|
||||
export default function(options: Schema): Rule {
|
||||
|
@ -9,14 +9,16 @@
|
||||
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
|
||||
|
||||
describe('ng-new schematic', () => {
|
||||
const schematicRunner =
|
||||
new SchematicTestRunner('@angular/bazel', require.resolve('../collection.json'), );
|
||||
const schematicRunner = new SchematicTestRunner(
|
||||
'@angular/bazel',
|
||||
require.resolve('../collection.json'),
|
||||
);
|
||||
const defaultOptions = {
|
||||
name: 'demo',
|
||||
version: '7.0.0',
|
||||
};
|
||||
|
||||
it('should call external @schematics/angular', async() => {
|
||||
it('should call external @schematics/angular', async () => {
|
||||
const options = {...defaultOptions};
|
||||
const host = await schematicRunner.runSchematicAsync('ng-new', options).toPromise();
|
||||
const {files} = host;
|
||||
@ -25,7 +27,7 @@ describe('ng-new schematic', () => {
|
||||
expect(files).toContain('/demo/package.json');
|
||||
});
|
||||
|
||||
it('should call ng-add to generate additional files needed by Bazel', async() => {
|
||||
it('should call ng-add to generate additional files needed by Bazel', async () => {
|
||||
const options = {...defaultOptions};
|
||||
const host = await schematicRunner.runSchematicAsync('ng-new', options).toPromise();
|
||||
const {files} = host;
|
||||
|
12
packages/bazel/src/schematics/ng-new/schema.d.ts
vendored
12
packages/bazel/src/schematics/ng-new/schema.d.ts
vendored
@ -86,8 +86,8 @@ export interface Schema {
|
||||
viewEncapsulation?: ViewEncapsulation;
|
||||
}
|
||||
/**
|
||||
* Initial git repository commit information.
|
||||
*/
|
||||
* Initial git repository commit information.
|
||||
*/
|
||||
export declare type CommitUnion = boolean | CommitObject;
|
||||
export interface CommitObject {
|
||||
email: string;
|
||||
@ -95,16 +95,16 @@ export interface CommitObject {
|
||||
name: string;
|
||||
}
|
||||
/**
|
||||
* The file extension or preprocessor to use for style files.
|
||||
*/
|
||||
* The file extension or preprocessor to use for style files.
|
||||
*/
|
||||
export declare enum Style {
|
||||
Css = 'css',
|
||||
Sass = 'sass',
|
||||
Scss = 'scss',
|
||||
}
|
||||
/**
|
||||
* The view encapsulation strategy to use in the initial project.
|
||||
*/
|
||||
* The view encapsulation strategy to use in the initial project.
|
||||
*/
|
||||
export declare enum ViewEncapsulation {
|
||||
Emulated = 'Emulated',
|
||||
Native = 'Native',
|
||||
|
@ -42,7 +42,7 @@ export function removeKeyValueInAstObject(
|
||||
let length = end - start;
|
||||
const match = content.slice(end).match(/^[,\s]+/);
|
||||
if (match) {
|
||||
length += match.pop() !.length;
|
||||
length += match.pop()!.length;
|
||||
}
|
||||
recorder.remove(start, length);
|
||||
if (i === node.properties.length - 1) { // last property
|
||||
@ -60,6 +60,6 @@ export function removeKeyValueInAstObject(
|
||||
/**
|
||||
* Returns true if the specified 'node' is a JsonAstObject, false otherwise.
|
||||
*/
|
||||
export function isJsonAstObject(node: JsonAstNode | null): node is JsonAstObject {
|
||||
export function isJsonAstObject(node: JsonAstNode|null): node is JsonAstObject {
|
||||
return !!node && node.kind === 'object';
|
||||
}
|
||||
|
@ -12,9 +12,10 @@ import {UnitTestTree} from '@angular-devkit/schematics/testing';
|
||||
import {isJsonAstObject, removeKeyValueInAstObject, replacePropertyInAstObject} from './json-utils';
|
||||
|
||||
describe('JsonUtils', () => {
|
||||
|
||||
let tree: UnitTestTree;
|
||||
beforeEach(() => { tree = new UnitTestTree(new HostTree()); });
|
||||
beforeEach(() => {
|
||||
tree = new UnitTestTree(new HostTree());
|
||||
});
|
||||
|
||||
describe('replacePropertyInAstObject', () => {
|
||||
it('should replace property', () => {
|
||||
|
@ -30,8 +30,9 @@ describe('@angular/common ng_package', () => {
|
||||
});
|
||||
// regression test for https://github.com/angular/angular/issues/23217
|
||||
// Note, we don't have an e2e test that covers this
|
||||
it('doesn\'t pass require in a way that breaks webpack static analysis',
|
||||
() => { expect(shx.cat('locales/fr.js')).not.toContain('factory(require, exports)'); });
|
||||
it('doesn\'t pass require in a way that breaks webpack static analysis', () => {
|
||||
expect(shx.cat('locales/fr.js')).not.toContain('factory(require, exports)');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have right bundle files', () => {
|
||||
@ -59,8 +60,9 @@ describe('@angular/common ng_package', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should reference core using global symbol in umd',
|
||||
() => { expect(shx.cat('bundles/common.umd.js')).toContain('global.ng.core'); });
|
||||
it('should reference core using global symbol in umd', () => {
|
||||
expect(shx.cat('bundles/common.umd.js')).toContain('global.ng.core');
|
||||
});
|
||||
|
||||
it('should have right fesm files', () => {
|
||||
const expected = [
|
||||
|
@ -41,8 +41,9 @@ describe('@angular/core ng_package', () => {
|
||||
describe('package.json', () => {
|
||||
const packageJson = 'package.json';
|
||||
|
||||
it('should have a package.json file',
|
||||
() => { expect(shx.grep('"name":', packageJson)).toContain(`@angular/core`); });
|
||||
it('should have a package.json file', () => {
|
||||
expect(shx.grep('"name":', packageJson)).toContain(`@angular/core`);
|
||||
});
|
||||
|
||||
it('should contain correct version number with the PLACEHOLDER string replaced', () => {
|
||||
expect(shx.grep('"version":', packageJson)).toMatch(/\d+\.\d+\.\d+(?!-PLACEHOLDER)/);
|
||||
@ -66,16 +67,20 @@ describe('@angular/core ng_package', () => {
|
||||
|
||||
describe('typescript support', () => {
|
||||
if (ivyEnabled) {
|
||||
it('should have an index d.ts file',
|
||||
() => { expect(shx.cat('core.d.ts')).toContain(`export *`); });
|
||||
it('should have an index d.ts file', () => {
|
||||
expect(shx.cat('core.d.ts')).toContain(`export *`);
|
||||
});
|
||||
|
||||
it('should not have amd module names',
|
||||
() => { expect(shx.cat('public_api.d.ts')).not.toContain('<amd-module name'); });
|
||||
it('should not have amd module names', () => {
|
||||
expect(shx.cat('public_api.d.ts')).not.toContain('<amd-module name');
|
||||
});
|
||||
} else {
|
||||
it('should have an index d.ts file',
|
||||
() => { expect(shx.cat('core.d.ts')).toContain('export declare'); });
|
||||
it('should have an r3_symbols d.ts file',
|
||||
() => { expect(shx.cat('src/r3_symbols.d.ts')).toContain('export declare'); });
|
||||
it('should have an index d.ts file', () => {
|
||||
expect(shx.cat('core.d.ts')).toContain('export declare');
|
||||
});
|
||||
it('should have an r3_symbols d.ts file', () => {
|
||||
expect(shx.cat('src/r3_symbols.d.ts')).toContain('export declare');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -87,15 +92,18 @@ describe('@angular/core ng_package', () => {
|
||||
|
||||
obsoleteInIvy('metadata files are no longer needed or produced in Ivy')
|
||||
.describe('angular metadata', () => {
|
||||
it('should have metadata.json files',
|
||||
() => { expect(shx.cat('core.metadata.json')).toContain(`"__symbolic":"module"`); });
|
||||
it('should not have self-references in metadata.json',
|
||||
() => { expect(shx.cat('core.metadata.json')).not.toContain(`"from":"./core"`); });
|
||||
it('should have metadata.json files', () => {
|
||||
expect(shx.cat('core.metadata.json')).toContain(`"__symbolic":"module"`);
|
||||
});
|
||||
it('should not have self-references in metadata.json', () => {
|
||||
expect(shx.cat('core.metadata.json')).not.toContain(`"from":"./core"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fesm2015', () => {
|
||||
it('should have a fesm15 file in the /fesm2015 directory',
|
||||
() => { expect(shx.cat('fesm2015/core.js')).toContain(`export {`); });
|
||||
it('should have a fesm15 file in the /fesm2015 directory', () => {
|
||||
expect(shx.cat('fesm2015/core.js')).toContain(`export {`);
|
||||
});
|
||||
|
||||
it('should have a source map', () => {
|
||||
expect(shx.cat('fesm2015/core.js.map'))
|
||||
@ -114,8 +122,9 @@ describe('@angular/core ng_package', () => {
|
||||
});
|
||||
|
||||
describe('fesm5', () => {
|
||||
it('should have a fesm5 file in the /fesm5 directory',
|
||||
() => { expect(shx.cat('fesm5/core.js')).toContain(`export {`); });
|
||||
it('should have a fesm5 file in the /fesm5 directory', () => {
|
||||
expect(shx.cat('fesm5/core.js')).toContain(`export {`);
|
||||
});
|
||||
|
||||
it('should have a source map', () => {
|
||||
expect(shx.cat('fesm5/core.js.map')).toContain(`{"version":3,"file":"core.js","sources":`);
|
||||
@ -131,12 +140,14 @@ describe('@angular/core ng_package', () => {
|
||||
expect(shx.cat('fesm5/core.js')).toContain('.ɵprov = ');
|
||||
});
|
||||
} else {
|
||||
it('should have decorators',
|
||||
() => { expect(shx.cat('fesm5/core.js')).toContain('__decorate'); });
|
||||
it('should have decorators', () => {
|
||||
expect(shx.cat('fesm5/core.js')).toContain('__decorate');
|
||||
});
|
||||
|
||||
// See: https://github.com/angular/angular/pull/32069
|
||||
it('should retain access to const',
|
||||
() => { expect(shx.cat('fesm5/core.js')).toContain('!ivyEnabled'); });
|
||||
it('should retain access to const', () => {
|
||||
expect(shx.cat('fesm5/core.js')).toContain('!ivyEnabled');
|
||||
});
|
||||
}
|
||||
|
||||
it('should load tslib from external bundle', () => {
|
||||
@ -145,8 +156,9 @@ describe('@angular/core ng_package', () => {
|
||||
});
|
||||
|
||||
obsoleteInIvy('we no longer need to export private symbols')
|
||||
.it('should have been built from the generated bundle index',
|
||||
() => { expect(shx.cat('fesm5/core.js')).toMatch('export {.*makeParamDecorator'); });
|
||||
.it('should have been built from the generated bundle index', () => {
|
||||
expect(shx.cat('fesm5/core.js')).toMatch('export {.*makeParamDecorator');
|
||||
});
|
||||
});
|
||||
|
||||
describe('esm2015', () => {
|
||||
@ -160,25 +172,31 @@ describe('@angular/core ng_package', () => {
|
||||
});
|
||||
|
||||
describe('esm5', () => {
|
||||
it('should not contain any *.ngfactory.js files',
|
||||
() => { expect(shx.find('esm5').filter(f => f.endsWith('.ngfactory.js'))).toEqual([]); });
|
||||
it('should not contain any *.ngfactory.js files', () => {
|
||||
expect(shx.find('esm5').filter(f => f.endsWith('.ngfactory.js'))).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not contain any *.ngsummary.js files',
|
||||
() => { expect(shx.find('esm5').filter(f => f.endsWith('.ngsummary.js'))).toEqual([]); });
|
||||
it('should not contain any *.ngsummary.js files', () => {
|
||||
expect(shx.find('esm5').filter(f => f.endsWith('.ngsummary.js'))).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('umd', () => {
|
||||
it('should have a umd file in the /bundles directory',
|
||||
() => { expect(shx.ls('bundles/core.umd.js').length).toBe(1, 'File not found'); });
|
||||
it('should have a umd file in the /bundles directory', () => {
|
||||
expect(shx.ls('bundles/core.umd.js').length).toBe(1, 'File not found');
|
||||
});
|
||||
|
||||
it('should have a source map next to the umd file',
|
||||
() => { expect(shx.ls('bundles/core.umd.js.map').length).toBe(1, 'File not found'); });
|
||||
it('should have a source map next to the umd file', () => {
|
||||
expect(shx.ls('bundles/core.umd.js.map').length).toBe(1, 'File not found');
|
||||
});
|
||||
|
||||
it('should have a minified umd file in the /bundles directory',
|
||||
() => { expect(shx.ls('bundles/core.umd.min.js').length).toBe(1, 'File not found'); });
|
||||
it('should have a minified umd file in the /bundles directory', () => {
|
||||
expect(shx.ls('bundles/core.umd.min.js').length).toBe(1, 'File not found');
|
||||
});
|
||||
|
||||
it('should have a source map next to the minified umd file',
|
||||
() => { expect(shx.ls('bundles/core.umd.min.js.map').length).toBe(1, 'File not found'); });
|
||||
it('should have a source map next to the minified umd file', () => {
|
||||
expect(shx.ls('bundles/core.umd.min.js.map').length).toBe(1, 'File not found');
|
||||
});
|
||||
|
||||
it('should have the version info in the header', () => {
|
||||
expect(shx.cat('bundles/core.umd.js'))
|
||||
@ -189,22 +207,25 @@ describe('@angular/core ng_package', () => {
|
||||
expect(shx.cat('bundles/core.umd.js')).toContain('function __extends');
|
||||
expect(shx.cat('bundles/core.umd.js')).not.toContain('undefined.__extends');
|
||||
});
|
||||
it('should have an AMD name',
|
||||
() => { expect(shx.cat('bundles/core.umd.js')).toContain('define(\'@angular/core\''); });
|
||||
it('should define ng global symbols',
|
||||
() => { expect(shx.cat('bundles/core.umd.js')).toContain('global.ng.core = {}'); });
|
||||
it('should have an AMD name', () => {
|
||||
expect(shx.cat('bundles/core.umd.js')).toContain('define(\'@angular/core\'');
|
||||
});
|
||||
it('should define ng global symbols', () => {
|
||||
expect(shx.cat('bundles/core.umd.js')).toContain('global.ng.core = {}');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('secondary entry-point', () => {
|
||||
describe('package.json', () => {
|
||||
const packageJson = p `testing/package.json`;
|
||||
const packageJson = p`testing/package.json`;
|
||||
|
||||
it('should have a package.json file',
|
||||
() => { expect(shx.grep('"name":', packageJson)).toContain(`@angular/core/testing`); });
|
||||
it('should have a package.json file', () => {
|
||||
expect(shx.grep('"name":', packageJson)).toContain(`@angular/core/testing`);
|
||||
});
|
||||
|
||||
it('should have its module resolution mappings defined in the nested package.json', () => {
|
||||
const packageJson = p `testing/package.json`;
|
||||
const packageJson = p`testing/package.json`;
|
||||
expect(shx.grep('"main":', packageJson)).toContain(`../bundles/core-testing.umd.js`);
|
||||
expect(shx.grep('"module":', packageJson)).toContain(`../fesm5/testing.js`);
|
||||
expect(shx.grep('"es2015":', packageJson)).toContain(`../fesm2015/testing.js`);
|
||||
@ -216,13 +237,15 @@ describe('@angular/core ng_package', () => {
|
||||
|
||||
describe('typings', () => {
|
||||
if (ivyEnabled) {
|
||||
const typingsFile = p `testing/index.d.ts`;
|
||||
it('should have a typings file',
|
||||
() => { expect(shx.cat(typingsFile)).toContain(`export * from './public_api';`); });
|
||||
const typingsFile = p`testing/index.d.ts`;
|
||||
it('should have a typings file', () => {
|
||||
expect(shx.cat(typingsFile)).toContain(`export * from './public_api';`);
|
||||
});
|
||||
} else {
|
||||
const typingsFile = p `testing/testing.d.ts`;
|
||||
it('should have a typings file',
|
||||
() => { expect(shx.cat(typingsFile)).toContain('export declare'); });
|
||||
const typingsFile = p`testing/testing.d.ts`;
|
||||
it('should have a typings file', () => {
|
||||
expect(shx.cat(typingsFile)).toContain('export declare');
|
||||
});
|
||||
}
|
||||
|
||||
obsoleteInIvy(
|
||||
@ -242,8 +265,9 @@ describe('@angular/core ng_package', () => {
|
||||
});
|
||||
|
||||
describe('fesm2015', () => {
|
||||
it('should have a fesm15 file in the /fesm2015 directory',
|
||||
() => { expect(shx.cat('fesm2015/testing.js')).toContain(`export {`); });
|
||||
it('should have a fesm15 file in the /fesm2015 directory', () => {
|
||||
expect(shx.cat('fesm2015/testing.js')).toContain(`export {`);
|
||||
});
|
||||
|
||||
it('should have a source map', () => {
|
||||
expect(shx.cat('fesm2015/testing.js.map'))
|
||||
@ -257,8 +281,9 @@ describe('@angular/core ng_package', () => {
|
||||
});
|
||||
|
||||
describe('fesm5', () => {
|
||||
it('should have a fesm5 file in the /fesm5 directory',
|
||||
() => { expect(shx.cat('fesm5/testing.js')).toContain(`export {`); });
|
||||
it('should have a fesm5 file in the /fesm5 directory', () => {
|
||||
expect(shx.cat('fesm5/testing.js')).toContain(`export {`);
|
||||
});
|
||||
|
||||
it('should have a source map', () => {
|
||||
expect(shx.cat('fesm5/testing.js.map'))
|
||||
@ -267,8 +292,9 @@ describe('@angular/core ng_package', () => {
|
||||
});
|
||||
|
||||
describe('umd', () => {
|
||||
it('should have a umd file in the /bundles directory',
|
||||
() => { expect(shx.ls('bundles/core-testing.umd.js').length).toBe(1, 'File not found'); });
|
||||
it('should have a umd file in the /bundles directory', () => {
|
||||
expect(shx.ls('bundles/core-testing.umd.js').length).toBe(1, 'File not found');
|
||||
});
|
||||
|
||||
it('should have a source map next to the umd file', () => {
|
||||
expect(shx.ls('bundles/core-testing.umd.js.map').length).toBe(1, 'File not found');
|
||||
|
@ -11,7 +11,6 @@ import {existsSync, readFileSync} from 'fs';
|
||||
import {dirname, join} from 'path';
|
||||
|
||||
describe('flat_module ng_module', () => {
|
||||
|
||||
let packageOutput: string;
|
||||
let flatModuleOutFile: string;
|
||||
|
||||
@ -21,11 +20,11 @@ describe('flat_module ng_module', () => {
|
||||
flatModuleOutFile = join(packageOutput, 'flat_module.js');
|
||||
});
|
||||
|
||||
it('should have a flat module out file',
|
||||
() => { expect(existsSync(flatModuleOutFile)).toBe(true); });
|
||||
it('should have a flat module out file', () => {
|
||||
expect(existsSync(flatModuleOutFile)).toBe(true);
|
||||
});
|
||||
|
||||
describe('flat module out file', () => {
|
||||
|
||||
obsoleteInIvy('Ngtsc computes the AMD module name differently than NGC')
|
||||
.it('should have a proper AMD module name', () => {
|
||||
expect(readFileSync(flatModuleOutFile, 'utf8'))
|
||||
|
@ -11,7 +11,6 @@ import * as path from 'path';
|
||||
import {setup} from './test_support';
|
||||
|
||||
describe('ngc_wrapped', () => {
|
||||
|
||||
it('should work', () => {
|
||||
const {read, write, runOneBuild, writeConfig, shouldExist, basePath, typesRoots} = setup();
|
||||
|
||||
|
@ -19,7 +19,9 @@ export interface TestSupport {
|
||||
angularCorePath: string;
|
||||
typesRoots: string;
|
||||
writeConfig({
|
||||
srcTargetPath, depPaths, pathMapping,
|
||||
srcTargetPath,
|
||||
depPaths,
|
||||
pathMapping,
|
||||
}: {
|
||||
srcTargetPath: string,
|
||||
depPaths?: string[],
|
||||
@ -33,13 +35,13 @@ export interface TestSupport {
|
||||
runOneBuild(): boolean;
|
||||
}
|
||||
|
||||
export function setup(
|
||||
{
|
||||
bazelBin = 'bazel-bin', tsconfig = 'tsconfig.json',
|
||||
}: {
|
||||
bazelBin?: string,
|
||||
tsconfig?: string,
|
||||
} = {}): TestSupport {
|
||||
export function setup({
|
||||
bazelBin = 'bazel-bin',
|
||||
tsconfig = 'tsconfig.json',
|
||||
}: {
|
||||
bazelBin?: string,
|
||||
tsconfig?: string,
|
||||
} = {}): TestSupport {
|
||||
const runfilesPath = process.env['TEST_SRCDIR'];
|
||||
|
||||
const basePath = makeTempDir(runfilesPath);
|
||||
@ -93,12 +95,17 @@ export function setup(
|
||||
}
|
||||
|
||||
function writeFiles(...mockDirs: {[fileName: string]: string}[]) {
|
||||
mockDirs.forEach(
|
||||
(dir) => { Object.keys(dir).forEach((fileName) => { write(fileName, dir[fileName]); }); });
|
||||
mockDirs.forEach((dir) => {
|
||||
Object.keys(dir).forEach((fileName) => {
|
||||
write(fileName, dir[fileName]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function writeConfig({
|
||||
srcTargetPath, depPaths = [], pathMapping = [],
|
||||
srcTargetPath,
|
||||
depPaths = [],
|
||||
pathMapping = [],
|
||||
}: {
|
||||
srcTargetPath: string,
|
||||
depPaths?: string[],
|
||||
@ -133,7 +140,8 @@ export function setup(
|
||||
defaultTsConfig: emptyTsConfig.config,
|
||||
rootDir: basePath,
|
||||
target: target,
|
||||
outDir: bazelBinPath, compilationTargetSrc,
|
||||
outDir: bazelBinPath,
|
||||
compilationTargetSrc,
|
||||
files: files,
|
||||
pathMapping: pathMappingObj,
|
||||
});
|
||||
@ -153,7 +161,9 @@ export function setup(
|
||||
}
|
||||
}
|
||||
|
||||
function runOneBuildImpl(): boolean { return runOneBuild(['@' + tsConfigJsonPath]); }
|
||||
function runOneBuildImpl(): boolean {
|
||||
return runOneBuild(['@' + tsConfigJsonPath]);
|
||||
}
|
||||
}
|
||||
|
||||
function makeTempDir(baseDir: string): string {
|
||||
|
@ -24,7 +24,7 @@ export {JsonFileReporter} from './src/reporter/json_file_reporter';
|
||||
export {MultiReporter} from './src/reporter/multi_reporter';
|
||||
export {Runner} from './src/runner';
|
||||
export {SampleDescription} from './src/sample_description';
|
||||
export {SampleState, Sampler} from './src/sampler';
|
||||
export {Sampler, SampleState} from './src/sampler';
|
||||
export {Validator} from './src/validator';
|
||||
export {RegressionSlopeValidator} from './src/validator/regression_slope_validator';
|
||||
export {SizeValidator} from './src/validator/size_validator';
|
||||
|
@ -14,18 +14,24 @@ export abstract class Metric {
|
||||
/**
|
||||
* Starts measuring
|
||||
*/
|
||||
beginMeasure(): Promise<any> { throw new Error('NYI'); }
|
||||
beginMeasure(): Promise<any> {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends measuring and reports the data
|
||||
* since the begin call.
|
||||
* @param restart: Whether to restart right after this.
|
||||
*/
|
||||
endMeasure(restart: boolean): Promise<{[key: string]: any}> { throw new Error('NYI'); }
|
||||
endMeasure(restart: boolean): Promise<{[key: string]: any}> {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the metrics provided by this metric implementation.
|
||||
* (e.g. units, ...)
|
||||
*/
|
||||
describe(): {[key: string]: string} { throw new Error('NYI'); }
|
||||
describe(): {[key: string]: string} {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,9 @@ export class MultiMetric extends Metric {
|
||||
];
|
||||
}
|
||||
|
||||
constructor(private _metrics: Metric[]) { super(); }
|
||||
constructor(private _metrics: Metric[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts measuring
|
||||
@ -56,7 +58,11 @@ export class MultiMetric extends Metric {
|
||||
|
||||
function mergeStringMaps(maps: {[key: string]: string}[]): {[key: string]: string} {
|
||||
const result: {[key: string]: string} = {};
|
||||
maps.forEach(map => { Object.keys(map).forEach(prop => { result[prop] = map[prop]; }); });
|
||||
maps.forEach(map => {
|
||||
Object.keys(map).forEach(prop => {
|
||||
result[prop] = map[prop];
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -23,11 +23,12 @@ export class PerflogMetric extends Metric {
|
||||
static PROVIDERS = [
|
||||
{
|
||||
provide: PerflogMetric,
|
||||
deps: [
|
||||
WebDriverExtension, PerflogMetric.SET_TIMEOUT, Options.MICRO_METRICS, Options.FORCE_GC,
|
||||
Options.CAPTURE_FRAMES, Options.RECEIVED_DATA, Options.REQUEST_COUNT,
|
||||
PerflogMetric.IGNORE_NAVIGATION
|
||||
]
|
||||
deps:
|
||||
[
|
||||
WebDriverExtension, PerflogMetric.SET_TIMEOUT, Options.MICRO_METRICS, Options.FORCE_GC,
|
||||
Options.CAPTURE_FRAMES, Options.RECEIVED_DATA, Options.REQUEST_COUNT,
|
||||
PerflogMetric.IGNORE_NAVIGATION
|
||||
]
|
||||
},
|
||||
{
|
||||
provide: PerflogMetric.SET_TIMEOUT,
|
||||
@ -169,7 +170,9 @@ export class PerflogMetric extends Metric {
|
||||
return result;
|
||||
}
|
||||
let resolve: (result: any) => void;
|
||||
const promise = new Promise<{[key: string]: number}>(res => { resolve = res; });
|
||||
const promise = new Promise<{[key: string]: number}>(res => {
|
||||
resolve = res;
|
||||
});
|
||||
this._setTimeout(() => resolve(this._readUntilEndMark(markName, loopCount + 1)), 100);
|
||||
return promise;
|
||||
});
|
||||
@ -188,7 +191,7 @@ export class PerflogMetric extends Metric {
|
||||
}
|
||||
startEvent['ph'] = 'B';
|
||||
endEvent['ph'] = 'E';
|
||||
endEvent['ts'] = startEvent['ts'] ! + startEvent['dur'] !;
|
||||
endEvent['ts'] = startEvent['ts']! + startEvent['dur']!;
|
||||
this._remainingEvents.push(startEvent);
|
||||
this._remainingEvents.push(endEvent);
|
||||
} else {
|
||||
@ -198,7 +201,7 @@ export class PerflogMetric extends Metric {
|
||||
if (needSort) {
|
||||
// Need to sort because of the ph==='X' events
|
||||
this._remainingEvents.sort((a, b) => {
|
||||
const diff = a['ts'] ! - b['ts'] !;
|
||||
const diff = a['ts']! - b['ts']!;
|
||||
return diff > 0 ? 1 : diff < 0 ? -1 : 0;
|
||||
});
|
||||
}
|
||||
@ -230,8 +233,8 @@ export class PerflogMetric extends Metric {
|
||||
result['requestCount'] = 0;
|
||||
}
|
||||
|
||||
let markStartEvent: PerfLogEvent = null !;
|
||||
let markEndEvent: PerfLogEvent = null !;
|
||||
let markStartEvent: PerfLogEvent = null!;
|
||||
let markEndEvent: PerfLogEvent = null!;
|
||||
events.forEach((event) => {
|
||||
const ph = event['ph'];
|
||||
const name = event['name'];
|
||||
@ -267,7 +270,7 @@ export class PerflogMetric extends Metric {
|
||||
let inMeasureRange = false;
|
||||
events.forEach((event) => {
|
||||
const ph = event['ph'];
|
||||
let name = event['name'] !;
|
||||
let name = event['name']!;
|
||||
let microIterations = 1;
|
||||
const microIterationsMatch = name.match(_MICRO_ITERATIONS_REGEX);
|
||||
if (microIterationsMatch) {
|
||||
@ -286,7 +289,7 @@ export class PerflogMetric extends Metric {
|
||||
if (this._requestCount && name === 'sendRequest') {
|
||||
result['requestCount'] += 1;
|
||||
} else if (this._receivedData && name === 'receivedData' && ph === 'I') {
|
||||
result['receivedData'] += event['args'] !['encodedDataLength'] !;
|
||||
result['receivedData'] += event['args']!['encodedDataLength']!;
|
||||
}
|
||||
if (ph === 'B' && name === _MARK_NAME_FRAME_CAPTURE) {
|
||||
if (frameCaptureStartEvent) {
|
||||
@ -305,7 +308,7 @@ export class PerflogMetric extends Metric {
|
||||
}
|
||||
|
||||
if (ph === 'I' && frameCaptureStartEvent && !frameCaptureEndEvent && name === 'frame') {
|
||||
frameTimestamps.push(event['ts'] !);
|
||||
frameTimestamps.push(event['ts']!);
|
||||
if (frameTimestamps.length >= 2) {
|
||||
frameTimes.push(
|
||||
frameTimestamps[frameTimestamps.length - 1] -
|
||||
@ -324,14 +327,14 @@ export class PerflogMetric extends Metric {
|
||||
intervalStartCount[name]--;
|
||||
if (intervalStartCount[name] === 0) {
|
||||
const startEvent = intervalStarts[name];
|
||||
const duration = (event['ts'] ! - startEvent['ts'] !);
|
||||
intervalStarts[name] = null !;
|
||||
const duration = (event['ts']! - startEvent['ts']!);
|
||||
intervalStarts[name] = null!;
|
||||
if (name === 'gc') {
|
||||
result['gcTime'] += duration;
|
||||
const amount =
|
||||
(startEvent['args'] !['usedHeapSize'] ! - event['args'] !['usedHeapSize'] !) / 1000;
|
||||
(startEvent['args']!['usedHeapSize']! - event['args']!['usedHeapSize']!) / 1000;
|
||||
result['gcAmount'] += amount;
|
||||
const majorGc = event['args'] !['majorGc'];
|
||||
const majorGc = event['args']!['majorGc'];
|
||||
if (majorGc && majorGc) {
|
||||
result['majorGcTime'] += duration;
|
||||
}
|
||||
@ -374,7 +377,9 @@ export class PerflogMetric extends Metric {
|
||||
frameTimes.filter(t => t < _FRAME_TIME_SMOOTH_THRESHOLD).length / frameTimes.length;
|
||||
}
|
||||
|
||||
private _markName(index: number) { return `${_MARK_NAME_PREFIX}${index}`; }
|
||||
private _markName(index: number) {
|
||||
return `${_MARK_NAME_PREFIX}${index}`;
|
||||
}
|
||||
}
|
||||
|
||||
const _MICRO_ITERATIONS_REGEX = /(.+)\*(\d+)$/;
|
||||
|
@ -26,7 +26,9 @@ export class UserMetric extends Metric {
|
||||
/**
|
||||
* Starts measuring
|
||||
*/
|
||||
beginMeasure(): Promise<any> { return Promise.resolve(true); }
|
||||
beginMeasure(): Promise<any> {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends measuring.
|
||||
@ -34,8 +36,7 @@ export class UserMetric extends Metric {
|
||||
endMeasure(restart: boolean): Promise<{[key: string]: any}> {
|
||||
let resolve: (result: any) => void;
|
||||
let reject: (error: any) => void;
|
||||
const promise = new Promise < { [key: string]: any; }
|
||||
> ((res, rej) => {
|
||||
const promise = new Promise<{[key: string]: any;}>((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
@ -67,5 +68,7 @@ export class UserMetric extends Metric {
|
||||
* Describes the metrics provided by this metric implementation.
|
||||
* (e.g. units, ...)
|
||||
*/
|
||||
describe(): {[key: string]: any} { return this._userMetrics; }
|
||||
describe(): {[key: string]: any} {
|
||||
return this._userMetrics;
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,9 @@ import {MeasureValues} from './measure_values';
|
||||
* A reporter reports measure values and the valid sample.
|
||||
*/
|
||||
export abstract class Reporter {
|
||||
reportMeasureValues(values: MeasureValues): Promise<any> { throw new Error('NYI'); }
|
||||
reportMeasureValues(values: MeasureValues): Promise<any> {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
|
||||
reportSample(completeSample: MeasureValues[], validSample: MeasureValues[]): Promise<any> {
|
||||
throw new Error('NYI');
|
||||
|
@ -28,10 +28,11 @@ export class ConsoleReporter extends Reporter {
|
||||
},
|
||||
{provide: ConsoleReporter.COLUMN_WIDTH, useValue: 18}, {
|
||||
provide: ConsoleReporter.PRINT,
|
||||
useValue: function(v: any) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(v);
|
||||
}
|
||||
useValue:
|
||||
function(v: any) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log(v);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@ -58,7 +59,9 @@ export class ConsoleReporter extends Reporter {
|
||||
this._print(`BENCHMARK ${sampleDescription.id}`);
|
||||
this._print('Description:');
|
||||
const props = sortedProps(sampleDescription.description);
|
||||
props.forEach((prop) => { this._print(`- ${prop}: ${sampleDescription.description[prop]}`); });
|
||||
props.forEach((prop) => {
|
||||
this._print(`- ${prop}: ${sampleDescription.description[prop]}`);
|
||||
});
|
||||
this._print('Metrics:');
|
||||
this._metricNames.forEach((metricName) => {
|
||||
this._print(`- ${metricName}: ${sampleDescription.metrics[metricName]}`);
|
||||
|
@ -37,7 +37,9 @@ export class JsonFileReporter extends Reporter {
|
||||
super();
|
||||
}
|
||||
|
||||
reportMeasureValues(measureValues: MeasureValues): Promise<any> { return Promise.resolve(null); }
|
||||
reportMeasureValues(measureValues: MeasureValues): Promise<any> {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
reportSample(completeSample: MeasureValues[], validSample: MeasureValues[]): Promise<any> {
|
||||
const stats: {[key: string]: string} = {};
|
||||
|
@ -27,7 +27,9 @@ export class MultiReporter extends Reporter {
|
||||
];
|
||||
}
|
||||
|
||||
constructor(private _reporters: Reporter[]) { super(); }
|
||||
constructor(private _reporters: Reporter[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
reportMeasureValues(values: MeasureValues): Promise<any[]> {
|
||||
return Promise.all(this._reporters.map(reporter => reporter.reportMeasureValues(values)));
|
||||
|
@ -17,7 +17,7 @@ import {Reporter} from './reporter';
|
||||
import {ConsoleReporter} from './reporter/console_reporter';
|
||||
import {MultiReporter} from './reporter/multi_reporter';
|
||||
import {SampleDescription} from './sample_description';
|
||||
import {SampleState, Sampler} from './sampler';
|
||||
import {Sampler, SampleState} from './sampler';
|
||||
import {Validator} from './validator';
|
||||
import {RegressionSlopeValidator} from './validator/regression_slope_validator';
|
||||
import {SizeValidator} from './validator/size_validator';
|
||||
|
@ -29,10 +29,11 @@ export class SampleDescription {
|
||||
userDesc
|
||||
],
|
||||
metric.describe()),
|
||||
deps: [
|
||||
Metric, Options.SAMPLE_ID, Options.FORCE_GC, Options.USER_AGENT, Validator,
|
||||
Options.DEFAULT_DESCRIPTION, Options.SAMPLE_DESCRIPTION
|
||||
]
|
||||
deps:
|
||||
[
|
||||
Metric, Options.SAMPLE_ID, Options.FORCE_GC, Options.USER_AGENT, Validator,
|
||||
Options.DEFAULT_DESCRIPTION, Options.SAMPLE_DESCRIPTION
|
||||
]
|
||||
}];
|
||||
description: {[key: string]: any};
|
||||
|
||||
@ -41,9 +42,13 @@ export class SampleDescription {
|
||||
public metrics: {[key: string]: any}) {
|
||||
this.description = {};
|
||||
descriptions.forEach(description => {
|
||||
Object.keys(description).forEach(prop => { this.description[prop] = description[prop]; });
|
||||
Object.keys(description).forEach(prop => {
|
||||
this.description[prop] = description[prop];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
toJson() { return {'id': this.id, 'description': this.description, 'metrics': this.metrics}; }
|
||||
toJson() {
|
||||
return {'id': this.id, 'description': this.description, 'metrics': this.metrics};
|
||||
}
|
||||
}
|
||||
|
@ -28,9 +28,11 @@ import {WebDriverAdapter} from './web_driver_adapter';
|
||||
export class Sampler {
|
||||
static PROVIDERS = <StaticProvider[]>[{
|
||||
provide: Sampler,
|
||||
deps: [
|
||||
WebDriverAdapter, Metric, Reporter, Validator, Options.PREPARE, Options.EXECUTE, Options.NOW
|
||||
]
|
||||
deps:
|
||||
[
|
||||
WebDriverAdapter, Metric, Reporter, Validator, Options.PREPARE, Options.EXECUTE,
|
||||
Options.NOW
|
||||
]
|
||||
}];
|
||||
constructor(
|
||||
private _driver: WebDriverAdapter, private _metric: Metric, private _reporter: Reporter,
|
||||
|
@ -17,11 +17,15 @@ export abstract class Validator {
|
||||
/**
|
||||
* Calculates a valid sample out of the complete sample
|
||||
*/
|
||||
validate(completeSample: MeasureValues[]): MeasureValues[]|null { throw new Error('NYI'); }
|
||||
validate(completeSample: MeasureValues[]): MeasureValues[]|null {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Map that describes the properties of the validator
|
||||
* (e.g. sample size, ...)
|
||||
*/
|
||||
describe(): {[key: string]: any} { throw new Error('NYI'); }
|
||||
describe(): {[key: string]: any} {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
}
|
||||
|
@ -22,9 +22,13 @@ export class SizeValidator extends Validator {
|
||||
{provide: SizeValidator.SAMPLE_SIZE, useValue: 10}
|
||||
];
|
||||
|
||||
constructor(@Inject(SizeValidator.SAMPLE_SIZE) private _sampleSize: number) { super(); }
|
||||
constructor(@Inject(SizeValidator.SAMPLE_SIZE) private _sampleSize: number) {
|
||||
super();
|
||||
}
|
||||
|
||||
describe(): {[key: string]: any} { return {'sampleSize': this._sampleSize}; }
|
||||
describe(): {[key: string]: any} {
|
||||
return {'sampleSize': this._sampleSize};
|
||||
}
|
||||
|
||||
validate(completeSample: MeasureValues[]): MeasureValues[]|null {
|
||||
if (completeSample.length >= this._sampleSize) {
|
||||
|
@ -14,9 +14,19 @@
|
||||
* Needs one implementation for every supported WebDriver client.
|
||||
*/
|
||||
export abstract class WebDriverAdapter {
|
||||
waitFor(callback: Function): Promise<any> { throw new Error('NYI'); }
|
||||
executeScript(script: string): Promise<any> { throw new Error('NYI'); }
|
||||
executeAsyncScript(script: string): Promise<any> { throw new Error('NYI'); }
|
||||
capabilities(): Promise<{[key: string]: any}> { throw new Error('NYI'); }
|
||||
logs(type: string): Promise<any[]> { throw new Error('NYI'); }
|
||||
waitFor(callback: Function): Promise<any> {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
executeScript(script: string): Promise<any> {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
executeAsyncScript(script: string): Promise<any> {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
capabilities(): Promise<{[key: string]: any}> {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
logs(type: string): Promise<any[]> {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import {Options} from './common_options';
|
||||
|
||||
export type PerfLogEvent = {
|
||||
[key: string]: any
|
||||
} & {
|
||||
}&{
|
||||
ph?: 'X' | 'B' | 'E' | 'I',
|
||||
ts?: number,
|
||||
dur?: number,
|
||||
@ -43,7 +43,7 @@ export abstract class WebDriverExtension {
|
||||
{
|
||||
provide: WebDriverExtension,
|
||||
useFactory: (children: WebDriverExtension[], capabilities: {[key: string]: any}) => {
|
||||
let delegate: WebDriverExtension = undefined !;
|
||||
let delegate: WebDriverExtension = undefined!;
|
||||
children.forEach(extension => {
|
||||
if (extension.supports(capabilities)) {
|
||||
delegate = extension;
|
||||
@ -60,11 +60,17 @@ export abstract class WebDriverExtension {
|
||||
return res;
|
||||
}
|
||||
|
||||
gc(): Promise<any> { throw new Error('NYI'); }
|
||||
gc(): Promise<any> {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
|
||||
timeBegin(name: string): Promise<any> { throw new Error('NYI'); }
|
||||
timeBegin(name: string): Promise<any> {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
|
||||
timeEnd(name: string, restartName: string|null): Promise<any> { throw new Error('NYI'); }
|
||||
timeEnd(name: string, restartName: string|null): Promise<any> {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format:
|
||||
@ -78,11 +84,17 @@ export abstract class WebDriverExtension {
|
||||
* Based on [Chrome Trace Event
|
||||
*Format](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit)
|
||||
**/
|
||||
readPerfLog(): Promise<PerfLogEvent[]> { throw new Error('NYI'); }
|
||||
readPerfLog(): Promise<PerfLogEvent[]> {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
|
||||
perfLogFeatures(): PerfLogFeatures { throw new Error('NYI'); }
|
||||
perfLogFeatures(): PerfLogFeatures {
|
||||
throw new Error('NYI');
|
||||
}
|
||||
|
||||
supports(capabilities: {[key: string]: any}): boolean { return true; }
|
||||
supports(capabilities: {[key: string]: any}): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class PerfLogFeatures {
|
||||
|
@ -54,7 +54,9 @@ export class ChromeDriverExtension extends WebDriverExtension {
|
||||
return parseInt(v, 10);
|
||||
}
|
||||
|
||||
gc() { return this.driver.executeScript('window.gc()'); }
|
||||
gc() {
|
||||
return this.driver.executeScript('window.gc()');
|
||||
}
|
||||
|
||||
async timeBegin(name: string): Promise<any> {
|
||||
if (this._firstRun) {
|
||||
@ -108,7 +110,7 @@ export class ChromeDriverExtension extends WebDriverExtension {
|
||||
chromeEvents.forEach((event) => {
|
||||
const categories = this._parseCategories(event['cat']);
|
||||
const normalizedEvent = this._convertEvent(event, categories);
|
||||
if (normalizedEvent != null) normalizedEvents !.push(normalizedEvent);
|
||||
if (normalizedEvent != null) normalizedEvents!.push(normalizedEvent);
|
||||
});
|
||||
return normalizedEvents;
|
||||
}
|
||||
@ -184,7 +186,9 @@ export class ChromeDriverExtension extends WebDriverExtension {
|
||||
return null; // nothing useful in this event
|
||||
}
|
||||
|
||||
private _parseCategories(categories: string): string[] { return categories.split(','); }
|
||||
private _parseCategories(categories: string): string[] {
|
||||
return categories.split(',');
|
||||
}
|
||||
|
||||
private _isEvent(
|
||||
eventCategories: string[], eventName: string, expectedCategories: string[],
|
||||
|
@ -22,7 +22,9 @@ export class FirefoxDriverExtension extends WebDriverExtension {
|
||||
this._profilerStarted = false;
|
||||
}
|
||||
|
||||
gc() { return this._driver.executeScript('window.forceGC()'); }
|
||||
gc() {
|
||||
return this._driver.executeScript('window.forceGC()');
|
||||
}
|
||||
|
||||
timeBegin(name: string): Promise<any> {
|
||||
if (!this._profilerStarted) {
|
||||
@ -44,7 +46,9 @@ export class FirefoxDriverExtension extends WebDriverExtension {
|
||||
return this._driver.executeAsyncScript('var cb = arguments[0]; window.getProfile(cb);');
|
||||
}
|
||||
|
||||
perfLogFeatures(): PerfLogFeatures { return new PerfLogFeatures({render: true, gc: true}); }
|
||||
perfLogFeatures(): PerfLogFeatures {
|
||||
return new PerfLogFeatures({render: true, gc: true});
|
||||
}
|
||||
|
||||
supports(capabilities: {[key: string]: any}): boolean {
|
||||
return capabilities['browserName'].toLowerCase() === 'firefox';
|
||||
|
@ -15,9 +15,13 @@ import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_e
|
||||
export class IOsDriverExtension extends WebDriverExtension {
|
||||
static PROVIDERS = [{provide: IOsDriverExtension, deps: [WebDriverAdapter]}];
|
||||
|
||||
constructor(private _driver: WebDriverAdapter) { super(); }
|
||||
constructor(private _driver: WebDriverAdapter) {
|
||||
super();
|
||||
}
|
||||
|
||||
gc(): Promise<any> { throw new Error('Force GC is not supported on iOS'); }
|
||||
gc(): Promise<any> {
|
||||
throw new Error('Force GC is not supported on iOS');
|
||||
}
|
||||
|
||||
timeBegin(name: string): Promise<any> {
|
||||
return this._driver.executeScript(`console.time('${name}');`);
|
||||
@ -62,16 +66,16 @@ export class IOsDriverExtension extends WebDriverExtension {
|
||||
const endTime = record['endTime'];
|
||||
|
||||
if (type === 'FunctionCall' && (data == null || data['scriptName'] !== 'InjectedScript')) {
|
||||
events !.push(createStartEvent('script', startTime));
|
||||
events!.push(createStartEvent('script', startTime));
|
||||
endEvent = createEndEvent('script', endTime);
|
||||
} else if (type === 'Time') {
|
||||
events !.push(createMarkStartEvent(data['message'], startTime));
|
||||
events!.push(createMarkStartEvent(data['message'], startTime));
|
||||
} else if (type === 'TimeEnd') {
|
||||
events !.push(createMarkEndEvent(data['message'], startTime));
|
||||
events!.push(createMarkEndEvent(data['message'], startTime));
|
||||
} else if (
|
||||
type === 'RecalculateStyles' || type === 'Layout' || type === 'UpdateLayerTree' ||
|
||||
type === 'Paint' || type === 'Rasterize' || type === 'CompositeLayers') {
|
||||
events !.push(createStartEvent('render', startTime));
|
||||
events!.push(createStartEvent('render', startTime));
|
||||
endEvent = createEndEvent('render', endTime);
|
||||
}
|
||||
// Note: ios used to support GCEvent up until iOS 6 :-(
|
||||
@ -79,21 +83,22 @@ export class IOsDriverExtension extends WebDriverExtension {
|
||||
this._convertPerfRecordsToEvents(record['children'], events);
|
||||
}
|
||||
if (endEvent != null) {
|
||||
events !.push(endEvent);
|
||||
events!.push(endEvent);
|
||||
}
|
||||
});
|
||||
return events;
|
||||
}
|
||||
|
||||
perfLogFeatures(): PerfLogFeatures { return new PerfLogFeatures({render: true}); }
|
||||
perfLogFeatures(): PerfLogFeatures {
|
||||
return new PerfLogFeatures({render: true});
|
||||
}
|
||||
|
||||
supports(capabilities: {[key: string]: any}): boolean {
|
||||
return capabilities['browserName'].toLowerCase() === 'safari';
|
||||
}
|
||||
}
|
||||
|
||||
function createEvent(
|
||||
ph: 'X' | 'B' | 'E' | 'B' | 'E', name: string, time: number, args: any = null) {
|
||||
function createEvent(ph: 'X'|'B'|'E'|'B'|'E', name: string, time: number, args: any = null) {
|
||||
const result: PerfLogEvent = {
|
||||
'cat': 'timeline',
|
||||
'name': name,
|
||||
|
@ -21,11 +21,17 @@ export class SeleniumWebDriverAdapter extends WebDriverAdapter {
|
||||
deps: []
|
||||
}];
|
||||
|
||||
constructor(private _driver: any) { super(); }
|
||||
constructor(private _driver: any) {
|
||||
super();
|
||||
}
|
||||
|
||||
waitFor(callback: () => any): Promise<any> { return this._driver.call(callback); }
|
||||
waitFor(callback: () => any): Promise<any> {
|
||||
return this._driver.call(callback);
|
||||
}
|
||||
|
||||
executeScript(script: string): Promise<any> { return this._driver.executeScript(script); }
|
||||
executeScript(script: string): Promise<any> {
|
||||
return this._driver.executeScript(script);
|
||||
}
|
||||
|
||||
executeAsyncScript(script: string): Promise<any> {
|
||||
return this._driver.executeAsyncScript(script);
|
||||
@ -58,7 +64,9 @@ class Command {
|
||||
private parameters_: {[key: string]: any} = {};
|
||||
constructor(private name_: string) {}
|
||||
|
||||
getName() { return this.name_; }
|
||||
getName() {
|
||||
return this.name_;
|
||||
}
|
||||
|
||||
setParameter(name: string, value: any) {
|
||||
this.parameters_[name] = value;
|
||||
@ -70,7 +78,11 @@ class Command {
|
||||
return this;
|
||||
}
|
||||
|
||||
getParameter(key: string) { return this.parameters_[key]; }
|
||||
getParameter(key: string) {
|
||||
return this.parameters_[key];
|
||||
}
|
||||
|
||||
getParameters() { return this.parameters_; }
|
||||
getParameters() {
|
||||
return this.parameters_;
|
||||
}
|
||||
}
|
||||
|
@ -11,50 +11,52 @@ import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/te
|
||||
import {Injector, Metric, MultiMetric} from '../../index';
|
||||
|
||||
(function() {
|
||||
function createMetric(ids: any[]) {
|
||||
const m = Injector
|
||||
.create([
|
||||
ids.map(id => ({provide: id, useValue: new MockMetric(id)})),
|
||||
MultiMetric.provideWith(ids)
|
||||
])
|
||||
.get<MultiMetric>(MultiMetric);
|
||||
return Promise.resolve(m);
|
||||
}
|
||||
function createMetric(ids: any[]) {
|
||||
const m = Injector
|
||||
.create([
|
||||
ids.map(id => ({provide: id, useValue: new MockMetric(id)})),
|
||||
MultiMetric.provideWith(ids)
|
||||
])
|
||||
.get<MultiMetric>(MultiMetric);
|
||||
return Promise.resolve(m);
|
||||
}
|
||||
|
||||
describe('multi metric', () => {
|
||||
it('should merge descriptions', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createMetric(['m1', 'm2']).then((m) => {
|
||||
expect(m.describe()).toEqual({'m1': 'describe', 'm2': 'describe'});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
describe('multi metric', () => {
|
||||
it('should merge descriptions', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createMetric(['m1', 'm2']).then((m) => {
|
||||
expect(m.describe()).toEqual({'m1': 'describe', 'm2': 'describe'});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should merge all beginMeasure calls',
|
||||
it('should merge all beginMeasure calls',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createMetric(['m1', 'm2']).then((m) => m.beginMeasure()).then((values) => {
|
||||
expect(values).toEqual(['m1_beginMeasure', 'm2_beginMeasure']);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
[false, true].forEach((restartFlag) => {
|
||||
it(`should merge all endMeasure calls for restart=${restartFlag}`,
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createMetric(['m1', 'm2']).then((m) => m.beginMeasure()).then((values) => {
|
||||
expect(values).toEqual(['m1_beginMeasure', 'm2_beginMeasure']);
|
||||
createMetric(['m1', 'm2']).then((m) => m.endMeasure(restartFlag)).then((values) => {
|
||||
expect(values).toEqual({'m1': {'restart': restartFlag}, 'm2': {'restart': restartFlag}});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
[false, true].forEach((restartFlag) => {
|
||||
it(`should merge all endMeasure calls for restart=${restartFlag}`,
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createMetric(['m1', 'm2']).then((m) => m.endMeasure(restartFlag)).then((values) => {
|
||||
expect(values).toEqual(
|
||||
{'m1': {'restart': restartFlag}, 'm2': {'restart': restartFlag}});
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
class MockMetric extends Metric {
|
||||
constructor(private _id: string) { super(); }
|
||||
constructor(private _id: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
beginMeasure(): Promise<string> { return Promise.resolve(`${this._id}_beginMeasure`); }
|
||||
beginMeasure(): Promise<string> {
|
||||
return Promise.resolve(`${this._id}_beginMeasure`);
|
||||
}
|
||||
|
||||
endMeasure(restart: boolean): Promise<{[key: string]: any}> {
|
||||
const result: {[key: string]: any} = {};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -12,55 +12,55 @@ import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/te
|
||||
import {Options, PerfLogEvent, PerfLogFeatures, UserMetric, WebDriverAdapter} from '../../index';
|
||||
|
||||
(function() {
|
||||
let wdAdapter: MockDriverAdapter;
|
||||
let wdAdapter: MockDriverAdapter;
|
||||
|
||||
function createMetric(
|
||||
perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures,
|
||||
{userMetrics}: {userMetrics?: {[key: string]: string}} = {}): UserMetric {
|
||||
if (!perfLogFeatures) {
|
||||
perfLogFeatures =
|
||||
new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true});
|
||||
}
|
||||
if (!userMetrics) {
|
||||
userMetrics = {};
|
||||
}
|
||||
wdAdapter = new MockDriverAdapter();
|
||||
const providers: StaticProvider[] = [
|
||||
Options.DEFAULT_PROVIDERS, UserMetric.PROVIDERS,
|
||||
{provide: Options.USER_METRICS, useValue: userMetrics},
|
||||
{provide: WebDriverAdapter, useValue: wdAdapter}
|
||||
];
|
||||
return Injector.create(providers).get(UserMetric);
|
||||
function createMetric(
|
||||
perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures,
|
||||
{userMetrics}: {userMetrics?: {[key: string]: string}} = {}): UserMetric {
|
||||
if (!perfLogFeatures) {
|
||||
perfLogFeatures =
|
||||
new PerfLogFeatures({render: true, gc: true, frameCapture: true, userTiming: true});
|
||||
}
|
||||
if (!userMetrics) {
|
||||
userMetrics = {};
|
||||
}
|
||||
wdAdapter = new MockDriverAdapter();
|
||||
const providers: StaticProvider[] = [
|
||||
Options.DEFAULT_PROVIDERS, UserMetric.PROVIDERS,
|
||||
{provide: Options.USER_METRICS, useValue: userMetrics},
|
||||
{provide: WebDriverAdapter, useValue: wdAdapter}
|
||||
];
|
||||
return Injector.create(providers).get(UserMetric);
|
||||
}
|
||||
|
||||
describe('user metric', () => {
|
||||
|
||||
it('should describe itself based on userMetrics', () => {
|
||||
expect(createMetric([[]], new PerfLogFeatures(), {
|
||||
userMetrics: {'loadTime': 'time to load'}
|
||||
}).describe())
|
||||
.toEqual({'loadTime': 'time to load'});
|
||||
});
|
||||
|
||||
describe('endMeasure', () => {
|
||||
it('should stop measuring when all properties have numeric values',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const metric = createMetric(
|
||||
[[]], new PerfLogFeatures(),
|
||||
{userMetrics: {'loadTime': 'time to load', 'content': 'time to see content'}});
|
||||
metric.beginMeasure().then(() => metric.endMeasure(true)).then(values => {
|
||||
expect(values['loadTime']).toBe(25);
|
||||
expect(values['content']).toBe(250);
|
||||
async.done();
|
||||
});
|
||||
|
||||
wdAdapter.data['loadTime'] = 25;
|
||||
// Wait before setting 2nd property.
|
||||
setTimeout(() => { wdAdapter.data['content'] = 250; }, 50);
|
||||
|
||||
}), 600);
|
||||
});
|
||||
describe('user metric', () => {
|
||||
it('should describe itself based on userMetrics', () => {
|
||||
expect(createMetric([[]], new PerfLogFeatures(), {
|
||||
userMetrics: {'loadTime': 'time to load'}
|
||||
}).describe())
|
||||
.toEqual({'loadTime': 'time to load'});
|
||||
});
|
||||
|
||||
describe('endMeasure', () => {
|
||||
it('should stop measuring when all properties have numeric values',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const metric = createMetric(
|
||||
[[]], new PerfLogFeatures(),
|
||||
{userMetrics: {'loadTime': 'time to load', 'content': 'time to see content'}});
|
||||
metric.beginMeasure().then(() => metric.endMeasure(true)).then(values => {
|
||||
expect(values['loadTime']).toBe(25);
|
||||
expect(values['content']).toBe(250);
|
||||
async.done();
|
||||
});
|
||||
|
||||
wdAdapter.data['loadTime'] = 25;
|
||||
// Wait before setting 2nd property.
|
||||
setTimeout(() => {
|
||||
wdAdapter.data['content'] = 250;
|
||||
}, 50);
|
||||
}), 600);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
class MockDriverAdapter extends WebDriverAdapter {
|
||||
|
@ -18,10 +18,10 @@ import {ConsoleReporter, Injector, MeasureValues, SampleDescription} from '../..
|
||||
|
||||
function createReporter(
|
||||
{columnWidth = null, sampleId = null, descriptions = null, metrics = null}: {
|
||||
columnWidth?: number | null,
|
||||
sampleId?: string | null,
|
||||
descriptions?: {[key: string]: any}[] | null,
|
||||
metrics?: {[key: string]: any} | null
|
||||
columnWidth?: number|null,
|
||||
sampleId?: string|null,
|
||||
descriptions?: {[key: string]: any}[]|null,
|
||||
metrics?: {[key: string]: any}|null
|
||||
}) {
|
||||
log = [];
|
||||
if (!descriptions) {
|
||||
@ -33,7 +33,7 @@ import {ConsoleReporter, Injector, MeasureValues, SampleDescription} from '../..
|
||||
const providers: StaticProvider[] = [
|
||||
ConsoleReporter.PROVIDERS, {
|
||||
provide: SampleDescription,
|
||||
useValue: new SampleDescription(sampleId, descriptions, metrics !)
|
||||
useValue: new SampleDescription(sampleId, descriptions, metrics!)
|
||||
},
|
||||
{provide: ConsoleReporter.PRINT, useValue: (line: string) => log.push(line)}
|
||||
];
|
||||
@ -84,7 +84,6 @@ import {ConsoleReporter, Injector, MeasureValues, SampleDescription} from '../..
|
||||
reporter.reportSample([], [mv(0, 0, {'a': 3, 'b': 0}), mv(1, 1, {'a': 5, 'b': 0})]);
|
||||
expect(log).toEqual(['======== | ========', '4.00+-25% | 0.00']);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -62,16 +62,12 @@ import {Injector, JsonFileReporter, MeasureValues, Options, SampleDescription} f
|
||||
{'timeStamp': '1970-01-01T00:00:00.000Z', 'runIndex': 0, 'values': {'a': 3, 'b': 6}}
|
||||
],
|
||||
'validSample': [
|
||||
{'timeStamp': '1970-01-01T00:00:00.000Z', 'runIndex': 0, 'values': {'a': 3, 'b': 6}}, {
|
||||
'timeStamp': '1970-01-01T00:00:00.001Z',
|
||||
'runIndex': 1,
|
||||
'values': {'a': 5, 'b': 9}
|
||||
}
|
||||
{'timeStamp': '1970-01-01T00:00:00.000Z', 'runIndex': 0, 'values': {'a': 3, 'b': 6}},
|
||||
{'timeStamp': '1970-01-01T00:00:00.001Z', 'runIndex': 1, 'values': {'a': 5, 'b': 9}}
|
||||
]
|
||||
});
|
||||
async.done();
|
||||
}));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -11,50 +11,48 @@ import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/te
|
||||
import {Injector, MeasureValues, MultiReporter, Reporter} from '../../index';
|
||||
|
||||
(function() {
|
||||
function createReporters(ids: any[]) {
|
||||
const r = Injector
|
||||
.create([
|
||||
ids.map(id => ({provide: id, useValue: new MockReporter(id)})),
|
||||
MultiReporter.provideWith(ids)
|
||||
])
|
||||
.get<MultiReporter>(MultiReporter);
|
||||
return Promise.resolve(r);
|
||||
}
|
||||
function createReporters(ids: any[]) {
|
||||
const r = Injector
|
||||
.create([
|
||||
ids.map(id => ({provide: id, useValue: new MockReporter(id)})),
|
||||
MultiReporter.provideWith(ids)
|
||||
])
|
||||
.get<MultiReporter>(MultiReporter);
|
||||
return Promise.resolve(r);
|
||||
}
|
||||
|
||||
describe('multi reporter', () => {
|
||||
describe('multi reporter', () => {
|
||||
it('should reportMeasureValues to all',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const mv = new MeasureValues(0, new Date(), {});
|
||||
createReporters(['m1', 'm2']).then((r) => r.reportMeasureValues(mv)).then((values) => {
|
||||
expect(values).toEqual([{'id': 'm1', 'values': mv}, {'id': 'm2', 'values': mv}]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reportMeasureValues to all',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const mv = new MeasureValues(0, new Date(), {});
|
||||
createReporters(['m1', 'm2']).then((r) => r.reportMeasureValues(mv)).then((values) => {
|
||||
it('should reportSample to call', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const completeSample =
|
||||
[new MeasureValues(0, new Date(), {}), new MeasureValues(1, new Date(), {})];
|
||||
const validSample = [completeSample[1]];
|
||||
|
||||
expect(values).toEqual([{'id': 'm1', 'values': mv}, {'id': 'm2', 'values': mv}]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should reportSample to call', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const completeSample =
|
||||
[new MeasureValues(0, new Date(), {}), new MeasureValues(1, new Date(), {})];
|
||||
const validSample = [completeSample[1]];
|
||||
|
||||
createReporters(['m1', 'm2'])
|
||||
.then((r) => r.reportSample(completeSample, validSample))
|
||||
.then((values) => {
|
||||
|
||||
expect(values).toEqual([
|
||||
{'id': 'm1', 'completeSample': completeSample, 'validSample': validSample},
|
||||
{'id': 'm2', 'completeSample': completeSample, 'validSample': validSample}
|
||||
]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
createReporters(['m1', 'm2'])
|
||||
.then((r) => r.reportSample(completeSample, validSample))
|
||||
.then((values) => {
|
||||
expect(values).toEqual([
|
||||
{'id': 'm1', 'completeSample': completeSample, 'validSample': validSample},
|
||||
{'id': 'm2', 'completeSample': completeSample, 'validSample': validSample}
|
||||
]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
})();
|
||||
|
||||
class MockReporter extends Reporter {
|
||||
constructor(private _id: string) { super(); }
|
||||
constructor(private _id: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
reportMeasureValues(values: MeasureValues): Promise<{[key: string]: any}> {
|
||||
return Promise.resolve({'id': this._id, 'values': values});
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
|
||||
|
||||
import {Injector, Metric, Options, Runner, SampleDescription, SampleState, Sampler, Validator, WebDriverAdapter} from '../index';
|
||||
import {Injector, Metric, Options, Runner, SampleDescription, Sampler, SampleState, Validator, WebDriverAdapter} from '../index';
|
||||
|
||||
{
|
||||
describe('runner', () => {
|
||||
@ -68,7 +68,6 @@ import {Injector, Metric, Options, Runner, SampleDescription, SampleState, Sampl
|
||||
.sample({id: 'someId'})
|
||||
.then((_) => injector.get(SampleDescription))
|
||||
.then((desc) => {
|
||||
|
||||
expect(desc.metrics).toEqual({'m1': 'some metric'});
|
||||
async.done();
|
||||
});
|
||||
@ -109,32 +108,45 @@ import {Injector, Metric, Options, Runner, SampleDescription, SampleState, Sampl
|
||||
})
|
||||
.then((_) => injector.get(SampleDescription))
|
||||
.then((desc) => {
|
||||
|
||||
expect(desc.description['a']).toBe(2);
|
||||
async.done();
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
class MockWebDriverAdapter extends WebDriverAdapter {
|
||||
executeScript(script: string): Promise<string> { return Promise.resolve('someUserAgent'); }
|
||||
capabilities(): Promise<Map<string, any>> { return null !; }
|
||||
executeScript(script: string): Promise<string> {
|
||||
return Promise.resolve('someUserAgent');
|
||||
}
|
||||
capabilities(): Promise<Map<string, any>> {
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
|
||||
class MockValidator extends Validator {
|
||||
constructor() { super(); }
|
||||
describe() { return {'v': 11}; }
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
describe() {
|
||||
return {'v': 11};
|
||||
}
|
||||
}
|
||||
|
||||
class MockMetric extends Metric {
|
||||
constructor() { super(); }
|
||||
describe() { return {'m1': 'some metric'}; }
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
describe() {
|
||||
return {'m1': 'some metric'};
|
||||
}
|
||||
}
|
||||
|
||||
class MockSampler extends Sampler {
|
||||
constructor() { super(null !, null !, null !, null !, null !, null !, null !); }
|
||||
sample(): Promise<SampleState> { return Promise.resolve(new SampleState([], [])); }
|
||||
constructor() {
|
||||
super(null!, null!, null!, null!, null!, null!, null!);
|
||||
}
|
||||
sample(): Promise<SampleState> {
|
||||
return Promise.resolve(new SampleState([], []));
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +67,6 @@ import {Injector, MeasureValues, Metric, Options, Reporter, Sampler, Validator,
|
||||
expect(log).toEqual([0, 1, 2, 3]);
|
||||
async.done();
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
it('should call prepare, beginMeasure, execute, endMeasure for every iteration',
|
||||
@ -77,8 +76,12 @@ import {Injector, MeasureValues, Metric, Options, Reporter, Sampler, Validator,
|
||||
createSampler({
|
||||
metric: createCountingMetric(log),
|
||||
validator: createCountingValidator(2),
|
||||
prepare: () => { log.push(`p${workCount++}`); },
|
||||
execute: () => { log.push(`w${workCount++}`); }
|
||||
prepare: () => {
|
||||
log.push(`p${workCount++}`);
|
||||
},
|
||||
execute: () => {
|
||||
log.push(`w${workCount++}`);
|
||||
}
|
||||
});
|
||||
sampler.sample().then((_) => {
|
||||
expect(log).toEqual([
|
||||
@ -102,7 +105,9 @@ import {Injector, MeasureValues, Metric, Options, Reporter, Sampler, Validator,
|
||||
createSampler({
|
||||
metric: createCountingMetric(log),
|
||||
validator: createCountingValidator(2),
|
||||
execute: () => { log.push(`w${workCount++}`); },
|
||||
execute: () => {
|
||||
log.push(`w${workCount++}`);
|
||||
},
|
||||
prepare: null
|
||||
});
|
||||
sampler.sample().then((_) => {
|
||||
@ -130,7 +135,9 @@ import {Injector, MeasureValues, Metric, Options, Reporter, Sampler, Validator,
|
||||
scriptTime = 0;
|
||||
return result;
|
||||
}),
|
||||
prepare: () => { scriptTime = 1 * iterationCount; },
|
||||
prepare: () => {
|
||||
scriptTime = 1 * iterationCount;
|
||||
},
|
||||
execute: () => {
|
||||
scriptTime = 10 * iterationCount;
|
||||
iterationCount++;
|
||||
@ -147,7 +154,7 @@ import {Injector, MeasureValues, Metric, Options, Reporter, Sampler, Validator,
|
||||
it('should call the validator for every execution and store the valid sample',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const log: any[] = [];
|
||||
const validSample = [mv(null !, null !, {})];
|
||||
const validSample = [mv(null!, null!, {})];
|
||||
|
||||
createSampler({
|
||||
metric: createCountingMetric(),
|
||||
@ -174,7 +181,7 @@ import {Injector, MeasureValues, Metric, Options, Reporter, Sampler, Validator,
|
||||
it('should report the metric values',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const log: any[] = [];
|
||||
const validSample = [mv(null !, null !, {})];
|
||||
const validSample = [mv(null!, null!, {})];
|
||||
createSampler({
|
||||
validator: createCountingValidator(2, validSample),
|
||||
metric: createCountingMetric(),
|
||||
@ -198,7 +205,6 @@ import {Injector, MeasureValues, Metric, Options, Reporter, Sampler, Validator,
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@ -223,7 +229,9 @@ function createCountingMetric(log: any[] = []) {
|
||||
}
|
||||
|
||||
class MockDriverAdapter extends WebDriverAdapter {
|
||||
constructor(private _log: any[] = [], private _waitFor: Function|null = null) { super(); }
|
||||
constructor(private _log: any[] = [], private _waitFor: Function|null = null) {
|
||||
super();
|
||||
}
|
||||
waitFor(callback: Function): Promise<any> {
|
||||
if (this._waitFor != null) {
|
||||
return this._waitFor(callback);
|
||||
@ -235,7 +243,9 @@ class MockDriverAdapter extends WebDriverAdapter {
|
||||
|
||||
|
||||
class MockValidator extends Validator {
|
||||
constructor(private _log: any[] = [], private _validate: Function|null = null) { super(); }
|
||||
constructor(private _log: any[] = [], private _validate: Function|null = null) {
|
||||
super();
|
||||
}
|
||||
validate(completeSample: MeasureValues[]): MeasureValues[] {
|
||||
const stableSample = this._validate != null ? this._validate(completeSample) : completeSample;
|
||||
this._log.push(['validate', completeSample, stableSample]);
|
||||
@ -244,7 +254,9 @@ class MockValidator extends Validator {
|
||||
}
|
||||
|
||||
class MockMetric extends Metric {
|
||||
constructor(private _log: any[] = [], private _endMeasure: Function|null = null) { super(); }
|
||||
constructor(private _log: any[] = [], private _endMeasure: Function|null = null) {
|
||||
super();
|
||||
}
|
||||
beginMeasure() {
|
||||
this._log.push(['beginMeasure']);
|
||||
return Promise.resolve(null);
|
||||
@ -257,7 +269,9 @@ class MockMetric extends Metric {
|
||||
}
|
||||
|
||||
class MockReporter extends Reporter {
|
||||
constructor(private _log: any[] = []) { super(); }
|
||||
constructor(private _log: any[] = []) {
|
||||
super();
|
||||
}
|
||||
reportMeasureValues(values: MeasureValues): Promise<any> {
|
||||
this._log.push(['reportMeasureValues', values]);
|
||||
return Promise.resolve(null);
|
||||
|
@ -11,7 +11,6 @@ import {Statistic} from '../src/statistic';
|
||||
|
||||
{
|
||||
describe('statistic', () => {
|
||||
|
||||
it('should calculate the mean', () => {
|
||||
expect(Statistic.calculateMean([])).toBeNaN();
|
||||
expect(Statistic.calculateMean([1, 2, 3])).toBe(2.0);
|
||||
@ -34,6 +33,5 @@ import {Statistic} from '../src/statistic';
|
||||
expect(Statistic.calculateRegressionSlope([1], 1, [2], 2)).toBeNaN();
|
||||
expect(Statistic.calculateRegressionSlope([1, 2], 1.5, [2, 4], 3)).toBe(2.0);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -20,13 +20,21 @@ export class TraceEventFactory {
|
||||
return res;
|
||||
}
|
||||
|
||||
markStart(name: string, time: number) { return this.create('B', name, time); }
|
||||
markStart(name: string, time: number) {
|
||||
return this.create('B', name, time);
|
||||
}
|
||||
|
||||
markEnd(name: string, time: number) { return this.create('E', name, time); }
|
||||
markEnd(name: string, time: number) {
|
||||
return this.create('E', name, time);
|
||||
}
|
||||
|
||||
start(name: string, time: number, args: any = null) { return this.create('B', name, time, args); }
|
||||
start(name: string, time: number, args: any = null) {
|
||||
return this.create('B', name, time, args);
|
||||
}
|
||||
|
||||
end(name: string, time: number, args: any = null) { return this.create('E', name, time, args); }
|
||||
end(name: string, time: number, args: any = null) {
|
||||
return this.create('E', name, time, args);
|
||||
}
|
||||
|
||||
instant(name: string, time: number, args: any = null) {
|
||||
return this.create('I', name, time, args);
|
||||
|
@ -53,7 +53,6 @@ import {Injector, MeasureValues, RegressionSlopeValidator} from '../../index';
|
||||
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2));
|
||||
expect(validator.validate(sample)).toEqual(sample.slice(1, 3));
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,6 @@ import {Injector, MeasureValues, SizeValidator} from '../../index';
|
||||
expect(validator.validate(sample.slice(0, 2))).toEqual(sample.slice(0, 2));
|
||||
expect(validator.validate(sample)).toEqual(sample.slice(1, 3));
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -11,44 +11,45 @@ import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/te
|
||||
import {Injector, Options, WebDriverExtension} from '../index';
|
||||
|
||||
(function() {
|
||||
function createExtension(ids: any[], caps: any) {
|
||||
return new Promise<any>((res, rej) => {
|
||||
try {
|
||||
res(Injector
|
||||
.create([
|
||||
ids.map((id) => ({provide: id, useValue: new MockExtension(id)})),
|
||||
{provide: Options.CAPABILITIES, useValue: caps},
|
||||
WebDriverExtension.provideFirstSupported(ids)
|
||||
])
|
||||
.get(WebDriverExtension));
|
||||
} catch (e) {
|
||||
rej(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('WebDriverExtension.provideFirstSupported', () => {
|
||||
|
||||
it('should provide the extension that matches the capabilities',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension(['m1', 'm2', 'm3'], {'browser': 'm2'}).then((m) => {
|
||||
expect(m.id).toEqual('m2');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw if there is no match',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension(['m1'], {'browser': 'm2'}).catch((err) => {
|
||||
expect(err != null).toBe(true);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
function createExtension(ids: any[], caps: any) {
|
||||
return new Promise<any>((res, rej) => {
|
||||
try {
|
||||
res(Injector
|
||||
.create([
|
||||
ids.map((id) => ({provide: id, useValue: new MockExtension(id)})),
|
||||
{provide: Options.CAPABILITIES, useValue: caps},
|
||||
WebDriverExtension.provideFirstSupported(ids)
|
||||
])
|
||||
.get(WebDriverExtension));
|
||||
} catch (e) {
|
||||
rej(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('WebDriverExtension.provideFirstSupported', () => {
|
||||
it('should provide the extension that matches the capabilities',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension(['m1', 'm2', 'm3'], {'browser': 'm2'}).then((m) => {
|
||||
expect(m.id).toEqual('m2');
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw if there is no match',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension(['m1'], {'browser': 'm2'}).catch((err) => {
|
||||
expect(err != null).toBe(true);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
});
|
||||
})();
|
||||
|
||||
class MockExtension extends WebDriverExtension {
|
||||
constructor(public id: string) { super(); }
|
||||
constructor(public id: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
supports(capabilities: {[key: string]: any}): boolean {
|
||||
return capabilities['browser'] === this.id;
|
||||
|
@ -32,7 +32,7 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
const normEvents = new TraceEventFactory('timeline', 'pid0');
|
||||
|
||||
function createExtension(
|
||||
perfRecords: any[] | null = null, userAgent: string | null = null,
|
||||
perfRecords: any[]|null = null, userAgent: string|null = null,
|
||||
messageMethod = 'Tracing.dataCollected'): WebDriverExtension {
|
||||
if (!perfRecords) {
|
||||
perfRecords = [];
|
||||
@ -97,9 +97,9 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
it('should mark the timeline via performance.mark() with start and end of a test',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension().timeEnd('name1', 'name2').then((_) => {
|
||||
expect(log).toEqual([[
|
||||
'executeScript', `performance.mark('name1-bpend');performance.mark('name2-bpstart');`
|
||||
]]);
|
||||
expect(log).toEqual([
|
||||
['executeScript', `performance.mark('name1-bpend');performance.mark('name2-bpstart');`]
|
||||
]);
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
@ -173,7 +173,8 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
[
|
||||
chromeTimelineV8Events.start('MajorGC', 1000, {'usedHeapSizeBefore': 1000}),
|
||||
chromeTimelineV8Events.end('MajorGC', 2000, {'usedHeapSizeAfter': 0}),
|
||||
], )
|
||||
],
|
||||
)
|
||||
.readPerfLog()
|
||||
.then((events) => {
|
||||
expect(events.length).toEqual(2);
|
||||
@ -192,7 +193,8 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
[
|
||||
chrome45TimelineEvents.start(recordType, 1234),
|
||||
chrome45TimelineEvents.end(recordType, 2345)
|
||||
], )
|
||||
],
|
||||
)
|
||||
.readPerfLog()
|
||||
.then((events) => {
|
||||
expect(events).toEqual([
|
||||
@ -210,7 +212,8 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
[
|
||||
chromeBlinkTimelineEvents.start('UpdateLayoutTree', 1234),
|
||||
chromeBlinkTimelineEvents.end('UpdateLayoutTree', 2345)
|
||||
], )
|
||||
],
|
||||
)
|
||||
.readPerfLog()
|
||||
.then((events) => {
|
||||
expect(events).toEqual([
|
||||
@ -254,8 +257,10 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
}));
|
||||
|
||||
it('should report receivedData', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([chrome45TimelineEvents.instant(
|
||||
'ResourceReceivedData', 1234, {'data': {'encodedDataLength': 987}})], )
|
||||
createExtension(
|
||||
[chrome45TimelineEvents.instant(
|
||||
'ResourceReceivedData', 1234, {'data': {'encodedDataLength': 987}})],
|
||||
)
|
||||
.readPerfLog()
|
||||
.then((events) => {
|
||||
expect(events).toEqual(
|
||||
@ -265,9 +270,11 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
}));
|
||||
|
||||
it('should report sendRequest', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
createExtension([chrome45TimelineEvents.instant(
|
||||
'ResourceSendRequest', 1234,
|
||||
{'data': {'url': 'http://here', 'requestMethod': 'GET'}})], )
|
||||
createExtension(
|
||||
[chrome45TimelineEvents.instant(
|
||||
'ResourceSendRequest', 1234,
|
||||
{'data': {'url': 'http://here', 'requestMethod': 'GET'}})],
|
||||
)
|
||||
.readPerfLog()
|
||||
.then((events) => {
|
||||
expect(events).toEqual([normEvents.instant(
|
||||
@ -277,7 +284,6 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
}));
|
||||
|
||||
describe('readPerfLog (common)', () => {
|
||||
|
||||
it('should execute a dummy script before reading them',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
// TODO(tbosch): This seems to be a bug in ChromeDriver:
|
||||
@ -296,7 +302,8 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
[
|
||||
chromeTimelineEvents.start(recordType, 1234),
|
||||
chromeTimelineEvents.end(recordType, 2345)
|
||||
], )
|
||||
],
|
||||
)
|
||||
.readPerfLog()
|
||||
.then((events) => {
|
||||
expect(events).toEqual([
|
||||
@ -337,7 +344,6 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
|
||||
it('should throw when ImplThreadRenderingStats contains more than one frame',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
|
||||
createExtension([benchmarkEvents.instant(
|
||||
'BenchmarkInstrumentation::ImplThreadRenderingStats', 1100,
|
||||
{'data': {'frame_count': 2}})])
|
||||
@ -349,7 +355,6 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
it('should report begin timestamps',
|
||||
@ -374,7 +379,6 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
|
||||
it('should throw an error on buffer overflow',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
|
||||
createExtension(
|
||||
[
|
||||
chromeTimelineEvents.start('FunctionCall', 1234),
|
||||
@ -394,9 +398,7 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
|
||||
expect(createExtension().supports({'browserName': 'Chrome'})).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@ -419,7 +421,7 @@ class MockDriverAdapter extends WebDriverAdapter {
|
||||
{'message': {'method': this._messageMethod, 'params': event}}, null, 2)
|
||||
})));
|
||||
} else {
|
||||
return null !;
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {AsyncTestCompleter, describe, expect, inject, it} from '@angular/core/testing/src/testing_internal';
|
||||
|
||||
import {IOsDriverExtension, Injector, WebDriverAdapter, WebDriverExtension} from '../../index';
|
||||
import {Injector, IOsDriverExtension, WebDriverAdapter, WebDriverExtension} from '../../index';
|
||||
import {TraceEventFactory} from '../trace_event_factory';
|
||||
|
||||
{
|
||||
@ -18,7 +18,7 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
|
||||
const normEvents = new TraceEventFactory('timeline', 'pid0');
|
||||
|
||||
function createExtension(perfRecords: any[] | null = null): WebDriverExtension {
|
||||
function createExtension(perfRecords: any[]|null = null): WebDriverExtension {
|
||||
if (!perfRecords) {
|
||||
perfRecords = [];
|
||||
}
|
||||
@ -63,7 +63,6 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
}));
|
||||
|
||||
describe('readPerfLog', () => {
|
||||
|
||||
it('should execute a dummy script before reading them',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
// TODO(tbosch): This seems to be a bug in ChromeDriver:
|
||||
@ -140,9 +139,7 @@ import {TraceEventFactory} from '../trace_event_factory';
|
||||
|
||||
expect(createExtension().supports({'browserName': 'Safari'})).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@ -155,7 +152,7 @@ function timeEndRecord(name: string, time: number) {
|
||||
}
|
||||
|
||||
function durationRecord(
|
||||
type: string, startTime: number, endTime: number, children: any[] | null = null) {
|
||||
type: string, startTime: number, endTime: number, children: any[]|null = null) {
|
||||
if (!children) {
|
||||
children = [];
|
||||
}
|
||||
@ -172,7 +169,9 @@ function internalScriptRecord(startTime: number, endTime: number) {
|
||||
}
|
||||
|
||||
class MockDriverAdapter extends WebDriverAdapter {
|
||||
constructor(private _log: any[], private _perfRecords: any[]) { super(); }
|
||||
constructor(private _log: any[], private _perfRecords: any[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
executeScript(script: string) {
|
||||
this._log.push(['executeScript', script]);
|
||||
@ -190,7 +189,7 @@ class MockDriverAdapter extends WebDriverAdapter {
|
||||
};
|
||||
}));
|
||||
} else {
|
||||
return null !;
|
||||
return null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -24,7 +24,7 @@ export class HttpHeaders {
|
||||
* Internal map of lowercase header names to values.
|
||||
*/
|
||||
// TODO(issue/24571): remove '!'.
|
||||
private headers !: Map<string, string[]>;
|
||||
private headers!: Map<string, string[]>;
|
||||
|
||||
|
||||
/**
|
||||
@ -36,7 +36,7 @@ export class HttpHeaders {
|
||||
/**
|
||||
* Complete the lazy initialization of this object (needed before reading).
|
||||
*/
|
||||
private lazyInit !: HttpHeaders | Function | null;
|
||||
private lazyInit!: HttpHeaders|Function|null;
|
||||
|
||||
/**
|
||||
* Queued updates to be materialized the next initialization.
|
||||
@ -59,7 +59,7 @@ export class HttpHeaders {
|
||||
const value = line.slice(index + 1).trim();
|
||||
this.maybeSetNormalizedName(name, key);
|
||||
if (this.headers.has(key)) {
|
||||
this.headers.get(key) !.push(value);
|
||||
this.headers.get(key)!.push(value);
|
||||
} else {
|
||||
this.headers.set(key, [value]);
|
||||
}
|
||||
@ -169,7 +169,7 @@ export class HttpHeaders {
|
||||
*
|
||||
* @returns A clone of the HTTP headers object with the given value deleted.
|
||||
*/
|
||||
delete (name: string, value?: string|string[]): HttpHeaders {
|
||||
delete(name: string, value?: string|string[]): HttpHeaders {
|
||||
return this.clone({name, value, op: 'd'});
|
||||
}
|
||||
|
||||
@ -197,8 +197,8 @@ export class HttpHeaders {
|
||||
private copyFrom(other: HttpHeaders) {
|
||||
other.init();
|
||||
Array.from(other.headers.keys()).forEach(key => {
|
||||
this.headers.set(key, other.headers.get(key) !);
|
||||
this.normalizedNames.set(key, other.normalizedNames.get(key) !);
|
||||
this.headers.set(key, other.headers.get(key)!);
|
||||
this.normalizedNames.set(key, other.normalizedNames.get(key)!);
|
||||
});
|
||||
}
|
||||
|
||||
@ -215,7 +215,7 @@ export class HttpHeaders {
|
||||
switch (update.op) {
|
||||
case 'a':
|
||||
case 's':
|
||||
let value = update.value !;
|
||||
let value = update.value!;
|
||||
if (typeof value === 'string') {
|
||||
value = [value];
|
||||
}
|
||||
@ -255,6 +255,6 @@ export class HttpHeaders {
|
||||
forEach(fn: (name: string, values: string[]) => void) {
|
||||
this.init();
|
||||
Array.from(this.normalizedNames.keys())
|
||||
.forEach(key => fn(this.normalizedNames.get(key) !, this.headers.get(key) !));
|
||||
.forEach(key => fn(this.normalizedNames.get(key)!, this.headers.get(key)!));
|
||||
}
|
||||
}
|
||||
|
@ -38,8 +38,8 @@ import {HttpEvent} from './response';
|
||||
* To use the same instance of `HttpInterceptors` for the entire app, import the `HttpClientModule`
|
||||
* only in your `AppModule`, and add the interceptors to the root application injector .
|
||||
* If you import `HttpClientModule` multiple times across different modules (for example, in lazy
|
||||
* loading modules), each import creates a new copy of the `HttpClientModule`, which overwrites the interceptors
|
||||
* provided in the root module.
|
||||
* loading modules), each import creates a new copy of the `HttpClientModule`, which overwrites the
|
||||
* interceptors provided in the root module.
|
||||
*
|
||||
*/
|
||||
export interface HttpInterceptor {
|
||||
|
@ -36,7 +36,9 @@ export const JSONP_ERR_WRONG_RESPONSE_TYPE = 'JSONP requests must use Json respo
|
||||
*
|
||||
*
|
||||
*/
|
||||
export abstract class JsonpCallbackContext { [key: string]: (data: any) => void; }
|
||||
export abstract class JsonpCallbackContext {
|
||||
[key: string]: (data: any) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an `HttpRequest` with the JSONP method,
|
||||
@ -53,7 +55,9 @@ export class JsonpClientBackend implements HttpBackend {
|
||||
/**
|
||||
* Get the name of the next callback method, by incrementing the global `nextRequestId`.
|
||||
*/
|
||||
private nextCallback(): string { return `ng_jsonp_callback_${nextRequestId++}`; }
|
||||
private nextCallback(): string {
|
||||
return `ng_jsonp_callback_${nextRequestId++}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a JSONP request and returns an event stream of the results.
|
||||
@ -157,7 +161,8 @@ export class JsonpClientBackend implements HttpBackend {
|
||||
observer.next(new HttpResponse({
|
||||
body,
|
||||
status: 200,
|
||||
statusText: 'OK', url,
|
||||
statusText: 'OK',
|
||||
url,
|
||||
}));
|
||||
|
||||
// Complete the stream, the response is over.
|
||||
@ -178,7 +183,8 @@ export class JsonpClientBackend implements HttpBackend {
|
||||
observer.error(new HttpErrorResponse({
|
||||
error,
|
||||
status: 0,
|
||||
statusText: 'JSONP Error', url,
|
||||
statusText: 'JSONP Error',
|
||||
url,
|
||||
}));
|
||||
};
|
||||
|
||||
|
@ -52,7 +52,7 @@ export class HttpInterceptingHandler implements HttpHandler {
|
||||
*
|
||||
*/
|
||||
export function interceptingHandler(
|
||||
backend: HttpBackend, interceptors: HttpInterceptor[] | null = []): HttpHandler {
|
||||
backend: HttpBackend, interceptors: HttpInterceptor[]|null = []): HttpHandler {
|
||||
if (!interceptors) {
|
||||
return backend;
|
||||
}
|
||||
|
@ -37,28 +37,36 @@ export class HttpUrlEncodingCodec implements HttpParameterCodec {
|
||||
* @param key The key name.
|
||||
* @returns The encoded key name.
|
||||
*/
|
||||
encodeKey(key: string): string { return standardEncoding(key); }
|
||||
encodeKey(key: string): string {
|
||||
return standardEncoding(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the value of a URL parameter or query-string.
|
||||
* @param value The value.
|
||||
* @returns The encoded value.
|
||||
*/
|
||||
encodeValue(value: string): string { return standardEncoding(value); }
|
||||
encodeValue(value: string): string {
|
||||
return standardEncoding(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes an encoded URL parameter or query-string key.
|
||||
* @param key The encoded key name.
|
||||
* @returns The decoded key name.
|
||||
*/
|
||||
decodeKey(key: string): string { return decodeURIComponent(key); }
|
||||
decodeKey(key: string): string {
|
||||
return decodeURIComponent(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes an encoded URL parameter or query-string value.
|
||||
* @param value The encoded value.
|
||||
* @returns The decoded value.
|
||||
*/
|
||||
decodeValue(value: string) { return decodeURIComponent(value); }
|
||||
decodeValue(value: string) {
|
||||
return decodeURIComponent(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -97,7 +105,8 @@ interface Update {
|
||||
op: 'a'|'d'|'s';
|
||||
}
|
||||
|
||||
/** Options used to construct an `HttpParams` instance.
|
||||
/**
|
||||
* Options used to construct an `HttpParams` instance.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
@ -109,7 +118,7 @@ export interface HttpParamsOptions {
|
||||
fromString?: string;
|
||||
|
||||
/** Object map of the HTTP parameters. Mutually exclusive with `fromString`. */
|
||||
fromObject?: {[param: string]: string | ReadonlyArray<string>};
|
||||
fromObject?: {[param: string]: string|ReadonlyArray<string>};
|
||||
|
||||
/** Encoding codec used to parse and serialize the parameters. */
|
||||
encoder?: HttpParameterCodec;
|
||||
@ -140,7 +149,7 @@ export class HttpParams {
|
||||
this.map = new Map<string, string[]>();
|
||||
Object.keys(options.fromObject).forEach(key => {
|
||||
const value = (options.fromObject as any)[key];
|
||||
this.map !.set(key, Array.isArray(value) ? value : [value]);
|
||||
this.map!.set(key, Array.isArray(value) ? value : [value]);
|
||||
});
|
||||
} else {
|
||||
this.map = null;
|
||||
@ -155,7 +164,7 @@ export class HttpParams {
|
||||
*/
|
||||
has(param: string): boolean {
|
||||
this.init();
|
||||
return this.map !.has(param);
|
||||
return this.map!.has(param);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,7 +175,7 @@ export class HttpParams {
|
||||
*/
|
||||
get(param: string): string|null {
|
||||
this.init();
|
||||
const res = this.map !.get(param);
|
||||
const res = this.map!.get(param);
|
||||
return !!res ? res[0] : null;
|
||||
}
|
||||
|
||||
@ -178,7 +187,7 @@ export class HttpParams {
|
||||
*/
|
||||
getAll(param: string): string[]|null {
|
||||
this.init();
|
||||
return this.map !.get(param) || null;
|
||||
return this.map!.get(param) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -187,7 +196,7 @@ export class HttpParams {
|
||||
*/
|
||||
keys(): string[] {
|
||||
this.init();
|
||||
return Array.from(this.map !.keys());
|
||||
return Array.from(this.map!.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,7 +205,9 @@ export class HttpParams {
|
||||
* @param value The new value to add.
|
||||
* @return A new body with the appended value.
|
||||
*/
|
||||
append(param: string, value: string): HttpParams { return this.clone({param, value, op: 'a'}); }
|
||||
append(param: string, value: string): HttpParams {
|
||||
return this.clone({param, value, op: 'a'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the value for a parameter.
|
||||
@ -204,7 +215,9 @@ export class HttpParams {
|
||||
* @param value The new value.
|
||||
* @return A new body with the new value.
|
||||
*/
|
||||
set(param: string, value: string): HttpParams { return this.clone({param, value, op: 's'}); }
|
||||
set(param: string, value: string): HttpParams {
|
||||
return this.clone({param, value, op: 's'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a given value or all values from a parameter.
|
||||
@ -213,7 +226,9 @@ export class HttpParams {
|
||||
* @return A new body with the given value removed, or with all values
|
||||
* removed if no value is specified.
|
||||
*/
|
||||
delete (param: string, value?: string): HttpParams { return this.clone({param, value, op: 'd'}); }
|
||||
delete(param: string, value?: string): HttpParams {
|
||||
return this.clone({param, value, op: 'd'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the body to an encoded string, where key-value pairs (separated by `=`) are
|
||||
@ -227,7 +242,7 @@ export class HttpParams {
|
||||
// `a: ['1']` produces `'a=1'`
|
||||
// `b: []` produces `''`
|
||||
// `c: ['1', '2']` produces `'c=1&c=2'`
|
||||
return this.map !.get(key) !.map(value => eKey + '=' + this.encoder.encodeValue(value))
|
||||
return this.map!.get(key)!.map(value => eKey + '=' + this.encoder.encodeValue(value))
|
||||
.join('&');
|
||||
})
|
||||
// filter out empty values because `b: []` produces `''`
|
||||
@ -237,7 +252,7 @@ export class HttpParams {
|
||||
}
|
||||
|
||||
private clone(update: Update): HttpParams {
|
||||
const clone = new HttpParams({ encoder: this.encoder } as HttpParamsOptions);
|
||||
const clone = new HttpParams({encoder: this.encoder} as HttpParamsOptions);
|
||||
clone.cloneFrom = this.cloneFrom || this;
|
||||
clone.updates = (this.updates || []).concat([update]);
|
||||
return clone;
|
||||
@ -249,29 +264,29 @@ export class HttpParams {
|
||||
}
|
||||
if (this.cloneFrom !== null) {
|
||||
this.cloneFrom.init();
|
||||
this.cloneFrom.keys().forEach(key => this.map !.set(key, this.cloneFrom !.map !.get(key) !));
|
||||
this.updates !.forEach(update => {
|
||||
this.cloneFrom.keys().forEach(key => this.map!.set(key, this.cloneFrom!.map!.get(key)!));
|
||||
this.updates!.forEach(update => {
|
||||
switch (update.op) {
|
||||
case 'a':
|
||||
case 's':
|
||||
const base = (update.op === 'a' ? this.map !.get(update.param) : undefined) || [];
|
||||
base.push(update.value !);
|
||||
this.map !.set(update.param, base);
|
||||
const base = (update.op === 'a' ? this.map!.get(update.param) : undefined) || [];
|
||||
base.push(update.value!);
|
||||
this.map!.set(update.param, base);
|
||||
break;
|
||||
case 'd':
|
||||
if (update.value !== undefined) {
|
||||
let base = this.map !.get(update.param) || [];
|
||||
let base = this.map!.get(update.param) || [];
|
||||
const idx = base.indexOf(update.value);
|
||||
if (idx !== -1) {
|
||||
base.splice(idx, 1);
|
||||
}
|
||||
if (base.length > 0) {
|
||||
this.map !.set(update.param, base);
|
||||
this.map!.set(update.param, base);
|
||||
} else {
|
||||
this.map !.delete(update.param);
|
||||
this.map!.delete(update.param);
|
||||
}
|
||||
} else {
|
||||
this.map !.delete(update.param);
|
||||
this.map!.delete(update.param);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ export class HttpRequest<T> {
|
||||
* Outgoing headers for this request.
|
||||
*/
|
||||
// TODO(issue/24571): remove '!'.
|
||||
readonly headers !: HttpHeaders;
|
||||
readonly headers!: HttpHeaders;
|
||||
|
||||
/**
|
||||
* Whether this request should be made in a way that exposes progress events.
|
||||
@ -121,7 +121,7 @@ export class HttpRequest<T> {
|
||||
* Outgoing URL parameters.
|
||||
*/
|
||||
// TODO(issue/24571): remove '!'.
|
||||
readonly params !: HttpParams;
|
||||
readonly params!: HttpParams;
|
||||
|
||||
/**
|
||||
* The outgoing URL with all URL parameters set.
|
||||
@ -312,7 +312,7 @@ export class HttpRequest<T> {
|
||||
body?: T|null,
|
||||
method?: string,
|
||||
url?: string,
|
||||
setHeaders?: {[name: string]: string | string[]},
|
||||
setHeaders?: {[name: string]: string|string[]},
|
||||
setParams?: {[param: string]: string},
|
||||
}): HttpRequest<T>;
|
||||
clone<V>(update: {
|
||||
@ -324,7 +324,7 @@ export class HttpRequest<T> {
|
||||
body?: V|null,
|
||||
method?: string,
|
||||
url?: string,
|
||||
setHeaders?: {[name: string]: string | string[]},
|
||||
setHeaders?: {[name: string]: string|string[]},
|
||||
setParams?: {[param: string]: string},
|
||||
}): HttpRequest<V>;
|
||||
clone(update: {
|
||||
@ -336,7 +336,7 @@ export class HttpRequest<T> {
|
||||
body?: any|null,
|
||||
method?: string,
|
||||
url?: string,
|
||||
setHeaders?: {[name: string]: string | string[]},
|
||||
setHeaders?: {[name: string]: string|string[]},
|
||||
setParams?: {[param: string]: string};
|
||||
} = {}): HttpRequest<any> {
|
||||
// For method, url, and responseType, take the current value unless
|
||||
@ -368,20 +368,23 @@ export class HttpRequest<T> {
|
||||
// Set every requested header.
|
||||
headers =
|
||||
Object.keys(update.setHeaders)
|
||||
.reduce((headers, name) => headers.set(name, update.setHeaders ![name]), headers);
|
||||
.reduce((headers, name) => headers.set(name, update.setHeaders![name]), headers);
|
||||
}
|
||||
|
||||
// Check whether the caller has asked to set params.
|
||||
if (update.setParams) {
|
||||
// Set every requested param.
|
||||
params = Object.keys(update.setParams)
|
||||
.reduce((params, param) => params.set(param, update.setParams ![param]), params);
|
||||
.reduce((params, param) => params.set(param, update.setParams![param]), params);
|
||||
}
|
||||
|
||||
// Finally, construct the new HttpRequest using the pieces from above.
|
||||
return new HttpRequest(
|
||||
method, url, body, {
|
||||
params, headers, reportProgress, responseType, withCredentials,
|
||||
});
|
||||
return new HttpRequest(method, url, body, {
|
||||
params,
|
||||
headers,
|
||||
reportProgress,
|
||||
responseType,
|
||||
withCredentials,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user