diff --git a/packages/vstory-animate/__tests__/unit/index.test.ts b/packages/vstory-animate/__tests__/unit/index.test.ts index 649b3158..1f528a9a 100644 --- a/packages/vstory-animate/__tests__/unit/index.test.ts +++ b/packages/vstory-animate/__tests__/unit/index.test.ts @@ -1,5 +1,48 @@ -describe('VChart Editor', () => { - it('should get the correct version', () => { - expect(1).toBeDefined(); +import { BarBounce } from '../../src/customAnimates/bar-bounce'; +import { BarLeap } from '../../src/customAnimates/bar-leap'; + +describe('custom bar animates', () => { + it('should not crash when BarBounce receives a null from rect', () => { + const animate = new BarBounce(null, { x: 20, x1: 60, y: 10, y1: 110 }, 1000, 'linear' as any, {}); + const out: Record = {}; + + animate.onUpdate(false, 0.5, out); + + expect(animate.getFromProps()).toMatchObject({ y: 110, y1: 110, x: 20, x1: 60 }); + expect(animate.getEndProps()).toMatchObject({ y: 10, y1: 110, x: 20, x1: 60 }); + expect(Number.isFinite(out.y)).toBe(true); + expect(Number.isFinite(out.y1)).toBe(true); + }); + + it('should keep BarBounce in vertical mode when y1 is zero', () => { + const animate = new BarBounce( + { x: 20, x1: 60, y: -40, y1: 0 }, + { x: 20, x1: 60, y: -40, y1: 0 }, + 1000, + 'linear' as any, + {} + ); + const out: Record = {}; + + animate.onUpdate(false, 0.5, out); + + expect(out.y).toBeDefined(); + expect(out.y1).toBeDefined(); + expect(out.x).toBeUndefined(); + }); + + it('should handle BarLeap horizontal bars without x1', () => { + const animate = new BarLeap(null, { x: 120, y: 40, width: 60, height: 20 }, 1000, 'linear' as any, {}); + const pathProxy = { + clear: jest.fn(), + moveTo: jest.fn(), + lineTo: jest.fn(), + quadraticCurveTo: jest.fn() + }; + + (animate as any).computePath(0.5, (animate as any).fromCenter, (animate as any).toCenter, pathProxy); + + expect(pathProxy.clear).toHaveBeenCalled(); + expect(pathProxy.moveTo).toHaveBeenCalled(); }); }); diff --git a/packages/vstory-animate/jest.config.js b/packages/vstory-animate/jest.config.js index e8723c54..708124c2 100644 --- a/packages/vstory-animate/jest.config.js +++ b/packages/vstory-animate/jest.config.js @@ -1,47 +1,7 @@ -// const path = require('path'); +const base = require('../../share/jest-config/jest.base'); -// module.exports = { -// runner: 'jest-electron/runner', -// testEnvironment: 'jest-electron/environment', -// testTimeout: 30000, -// testRegex: '/__tests__/.*test\\.ts?$', -// moduleFileExtensions: ['ts', 'js', 'json'], -// setupFilesAfterEnv: ['jest-extended/all'], -// preset: 'ts-jest', -// silent: true, -// globals: { -// 'ts-jest': { -// resolveJsonModule: true, -// esModuleInterop: true, -// experimentalDecorators: true, -// module: 'ESNext', -// tsconfig: './tsconfig.test.json' -// }, -// __DEV__: true -// }, -// setupFiles: ['./setup-mock.js'], -// verbose: true, -// coverageReporters: ['json-summary', 'lcov', 'text'], -// coveragePathIgnorePatterns: ['node_modules', 'demo', 'interface.ts', '.d.ts', 'typings'], -// testPathIgnorePatterns: ['demo'], -// collectCoverageFrom: [ -// '**/src/**', -// '!**/cjs/**', -// '!**/dist/**', -// '!**/es/**', -// '!**/node_modules/**', -// '!**/demo/**', -// '!**/interface/**', -// '!**/interface.ts', -// '!**/**.d.ts' -// ], -// coverageThreshold: { -// global: { -// branches: 80, -// functions: 80, -// lines: 80, -// statements: 80 -// } -// }, -// moduleNameMapper: {} -// }; +module.exports = { + ...base, + rootDir: __dirname, + setupFiles: ['./setup-mock.js'] +}; diff --git a/packages/vstory-animate/src/customAnimates/bar-bounce.ts b/packages/vstory-animate/src/customAnimates/bar-bounce.ts index c4b238a0..0e758650 100644 --- a/packages/vstory-animate/src/customAnimates/bar-bounce.ts +++ b/packages/vstory-animate/src/customAnimates/bar-bounce.ts @@ -1,5 +1,6 @@ import type { EasingType } from '@visactor/vrender'; import { ACustomAnimate, generatorPathEasingFunc } from '@visactor/vrender'; +import { createCollapsedBarRect, isVerticalBarRect, normalizeBarRect } from './bar-utils'; export const barBounce1Str = 'M0,0 C0.126,0.382 0.06,0.254 0.105,0.467 0.159,0.729 0.3,1.173 0.38,1.173 0.476,1.173 0.512,0.909 0.578,0.9 0.632,0.892 0.685,1.084 0.735,1.085 0.784,1.085 0.843,0.966 0.887,0.966 0.94,0.966 0.984,1 1,1'; @@ -18,19 +19,16 @@ export class BarBounce extends ACustomAnimate<{ y?: number; y1?: number; x?: num declare valid: boolean; constructor( - from: { y?: number; y1?: number; x?: number; x1?: number }, - to: { y?: number; y1?: number; x?: number; x1?: number }, + from: { y?: number; y1?: number; x?: number; x1?: number } | null, + to: { y?: number; y1?: number; x?: number; x1?: number } | null, duration: number, easing: EasingType, params: any ) { - const f = { - y: from.y1, - y1: from.y1, - x: from.x1, - x1: from.x1 - }; - super(f, { y: from.y, y1: from.y1, x: from.x, x1: from.x1 }, duration, easing, params); + const vertical = isVerticalBarRect(from, to); + const target = normalizeBarRect(to, from); + const f = createCollapsedBarRect(target, vertical); + super(f, target, duration, easing, params); } getEndProps(): Record { @@ -51,7 +49,7 @@ export class BarBounce extends ACustomAnimate<{ y?: number; y1?: number; x?: num const r1 = barBounce1!(ratio); const r2 = barBounce2!(ratio); // const - if (from.y1) { + if (to.y1 != null) { out.y = from.y! + (to.y! - from.y!) * r1; const height = to.y1! - to.y!; const dh = height * r2; diff --git a/packages/vstory-animate/src/customAnimates/bar-leap.ts b/packages/vstory-animate/src/customAnimates/bar-leap.ts index db5e9d7e..42a14ba6 100644 --- a/packages/vstory-animate/src/customAnimates/bar-leap.ts +++ b/packages/vstory-animate/src/customAnimates/bar-leap.ts @@ -1,6 +1,7 @@ import type { EasingType, ICustomPath2D } from '@visactor/vrender'; import { ACustomAnimate, CustomPath2D, generatorPathEasingFunc } from '@visactor/vrender'; import type { IPointLike } from '@visactor/vutils'; +import { isVerticalBarRect, normalizeBarRect } from './bar-utils'; export const barLeap1Str = 'M0,0 C0.083,0.163 0.179,1 0.6,1 0.814,1 0.898,1 1,1'; export const barLeap2Str = 'M0,0 C0.27,0 0.179,0 0.6,0 0.632,0 0.782,-0.132 0.818,-0.132 0.868,-0.132 0.972,0 1,0'; @@ -29,21 +30,23 @@ export class BarLeap extends ACustomAnimate<{ protected vertical: boolean; constructor( - from: { y: number; y1?: number; x: number; x1?: number; width?: number; height?: number }, - to: { y: number; y1?: number; x: number; x1?: number; width?: number; height?: number }, + from: { y: number; y1?: number; x: number; x1?: number; width?: number; height?: number } | null, + to: { y: number; y1?: number; x: number; x1?: number; width?: number; height?: number } | null, duration: number, easing: EasingType, params: any ) { - super({ ...from, cornerRadius: 0 }, to, duration, easing, params); - this.vertical = to.y1 != null; - const centerX = to.x1 != null ? (to.x + to.x1) / 2 : to.x + to.width! / 2; - const centerY = to.y1 != null ? (to.y + to.y1) / 2 : to.y + to.height! / 2; - this.toCenter = { x: centerX - to.x, y: centerY - to.y }; + const target = normalizeBarRect(to, from); + const start = normalizeBarRect(from, target); + super({ ...start, cornerRadius: 0 }, target, duration, easing, params); + this.vertical = isVerticalBarRect(from, to); + const centerX = (target.x! + target.x1!) / 2; + const centerY = (target.y! + target.y1!) / 2; + this.toCenter = { x: centerX - target.x!, y: centerY - target.y! }; if (this.vertical) { - this.fromCenter = { x: centerX + 200 - to.x, y: centerY - 600 - to.y }; + this.fromCenter = { x: centerX + 200 - target.x!, y: centerY - 600 - target.y! }; } else { - this.fromCenter = { x: centerX + 600 - to.x, y: centerY - 200 - to.y }; + this.fromCenter = { x: centerX + 600 - target.x!, y: centerY - 200 - target.y! }; } } diff --git a/packages/vstory-animate/src/customAnimates/bar-utils.ts b/packages/vstory-animate/src/customAnimates/bar-utils.ts new file mode 100644 index 00000000..bc68b366 --- /dev/null +++ b/packages/vstory-animate/src/customAnimates/bar-utils.ts @@ -0,0 +1,66 @@ +export interface IBarRectAnimateProps { + y?: number; + y1?: number; + x?: number; + x1?: number; + width?: number; + height?: number; + cornerRadius?: number; +} + +export interface INormalizedBarRectAnimateProps extends IBarRectAnimateProps { + y: number; + y1: number; + x: number; + x1: number; + width: number; + height: number; +} + +export function isVerticalBarRect(from?: IBarRectAnimateProps | null, to?: IBarRectAnimateProps | null) { + return to?.y1 != null || from?.y1 != null; +} + +export function normalizeBarRect( + rect?: IBarRectAnimateProps | null, + fallback?: IBarRectAnimateProps | null +): INormalizedBarRectAnimateProps { + const source = rect ?? {}; + const ref = fallback ?? {}; + + const x = source.x ?? ref.x ?? ref.x1 ?? 0; + const y = source.y ?? ref.y ?? ref.y1 ?? 0; + const x1 = source.x1 ?? ref.x1 ?? (source.width != null ? x + source.width : ref.width != null ? x + ref.width : x); + const y1 = + source.y1 ?? ref.y1 ?? (source.height != null ? y + source.height : ref.height != null ? y + ref.height : y); + const width = source.width ?? ref.width ?? Math.abs(x1 - x); + const height = source.height ?? ref.height ?? Math.abs(y1 - y); + + return { + ...ref, + ...source, + x, + y, + x1, + y1, + width, + height, + cornerRadius: source.cornerRadius ?? ref.cornerRadius + }; +} + +export function createCollapsedBarRect(rect: IBarRectAnimateProps, vertical: boolean): IBarRectAnimateProps { + if (vertical) { + return { + ...rect, + y: rect.y1 ?? rect.y ?? 0, + y1: rect.y1 ?? rect.y ?? 0 + }; + } + + return { + ...rect, + x: rect.x1 ?? rect.x ?? 0, + x1: rect.x1 ?? rect.x ?? 0 + }; +}