Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ npmPreapprovedPackages:
- 'babel-plugin-syntax-hermes-parser'
- 'hermes-parser'
- 'hermes-estree'
- 'tstyche'

tsEnableAutoTypes: false

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"lint:workspaces": "yarn workspaces foreach --all --exclude \"react-native-reanimated-monorepo\" --parallel --topological-dev run lint",
"lint:root": "eslint . --ignore-pattern 'packages/**' --ignore-pattern 'apps/**' --ignore-pattern 'docs/**' && yarn run --top-level oxfmt --check --ignore-path .prettiereslintignore .",
"prepare-vscode": "bash ./scripts/prepare-vscode.sh",
"type:check:tstyche": "tstyche",
"toggle-bundle-mode": "bash ./scripts/toggle-bundle-mode.sh"
},
"devDependencies": {
Expand Down Expand Up @@ -82,6 +83,7 @@
"remark-gfm": "4.0.1",
"remark-mdx": "3.1.0",
"shelljs": "0.10.0",
"tstyche": "7.2.1",
"typescript": "5.9.3"
},
"resolutions": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { describe, expect, test } from 'tstyche';

import { getStaticFeatureFlag } from '..';

describe('getStaticFeatureFlag', () => {
test('accepts a known static feature flag', () => {
expect(getStaticFeatureFlag).type.toBeCallableWith('RUNTIME_TEST_FLAG');
});

test('rejects an unknown feature flag', () => {
expect(getStaticFeatureFlag).type.not.toBeCallableWith('NON_EXISTENT_FLAG');
});
});
10 changes: 0 additions & 10 deletions packages/react-native-reanimated/__typetests__/FeatureFlagTest.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { ImageStyle, StyleProp, TextStyle, ViewStyle } from 'react-native';
import { describe, test } from 'tstyche';

import type { AnimatedStyle } from '..';
import { cubicBezier, linear, steps } from '..';
Expand All @@ -15,6 +16,9 @@ import { cubicBezier, linear, steps } from '..';
// `AnimatedStyle`, the intersection collapsed conflicting properties to
// `never` and surfaced as "Type … is not assignable to 'undefined'" on inline
// `style={{ ... }}` for `Animated.View`/`Animated.Text`/`Animated.Image`.
//
// These are compile-only checks: each typed assignment must type-check, which
// TSTyche enforces by type-checking the whole test file.

type ExpoStyleAugmentation = {
animationName?: string;
Expand Down Expand Up @@ -42,8 +46,8 @@ type AugmentedViewStyle = ViewStyle & ExpoStyleAugmentation;
type AugmentedTextStyle = TextStyle & ExpoStyleAugmentation;
type AugmentedImageStyle = ImageStyle & ExpoStyleAugmentation;

function AnimatedStyleAugmentationTest() {
function ParametrizedTimingFunctionsOnAugmentedView() {
describe('AnimatedStyle with Expo-augmented RN styles', () => {
test('parametrized timing functions on an augmented view style', () => {
const cubic: AnimatedStyle<AugmentedViewStyle> = {
width: 100,
backgroundColor: 'red',
Expand All @@ -67,16 +71,16 @@ function AnimatedStyleAugmentationTest() {
animationName: { from: { opacity: 0 }, to: { opacity: 1 } },
animationTimingFunction: steps(4),
};
}
});

function PredefinedTimingFunctionsOnAugmentedView() {
test('predefined timing functions on an augmented view style', () => {
const s: AnimatedStyle<AugmentedViewStyle> = {
animationName: { from: { opacity: 0 }, to: { opacity: 1 } },
animationTimingFunction: 'ease-in-out',
};
}
});

function ArrayShorthandsOnAugmentedView() {
test('array shorthands on an augmented view style', () => {
const s: AnimatedStyle<AugmentedViewStyle> = {
animationName: [
{ from: { opacity: 0 }, to: { opacity: 1 } },
Expand All @@ -88,9 +92,9 @@ function AnimatedStyleAugmentationTest() {
animationDuration: ['1s', '2s'],
animationTimingFunction: [cubicBezier(0.25, 0.1, 0.25, 1), 'ease'],
};
}
});

function CSSTransitionsOnAugmentedView() {
test('CSS transitions on an augmented view style', () => {
const s: AnimatedStyle<AugmentedViewStyle> = {
width: 100,
transitionProperty: 'width',
Expand All @@ -99,50 +103,46 @@ function AnimatedStyleAugmentationTest() {
transitionDelay: 0,
transitionBehavior: 'allow-discrete',
};
}
});

function CSSAnimationOnAugmentedText() {
test('CSS animation on an augmented text style', () => {
const s: AnimatedStyle<AugmentedTextStyle> = {
color: 'black',
animationName: { from: { opacity: 0 }, to: { opacity: 1 } },
animationTimingFunction: cubicBezier(0.25, 0.1, 0.25, 1),
};
}
});

function CSSAnimationOnAugmentedImage() {
test('CSS animation on an augmented image style', () => {
const s: AnimatedStyle<AugmentedImageStyle> = {
resizeMode: 'cover',
animationName: { from: { opacity: 0 }, to: { opacity: 1 } },
animationTimingFunction: cubicBezier(0.25, 0.1, 0.25, 1),
};
}
});

function NonCSSStylesUnaffected() {
test('non-CSS styles are unaffected', () => {
const s: AnimatedStyle<AugmentedViewStyle> = {
width: 100,
height: 100,
backgroundColor: 'blue',
transform: [{ translateX: 10 }, { scale: 2 }],
opacity: 0.5,
};
}
});

// Sanity-check: plain `ViewStyle` without the augmented CSS fields still works.
function PlainViewStyleStillWorks() {
test('plain ViewStyle without augmented fields still works', () => {
const s: AnimatedStyle<ViewStyle> = {
width: 100,
animationName: { from: { opacity: 0 }, to: { opacity: 1 } },
animationTimingFunction: cubicBezier(0.25, 0.1, 0.25, 1),
};
}
});

// `AnimatedStyle<StyleProp<…>>` is a union (null, arrays, etc.); the Omit
// must distribute over union members to strip augmented CSS keys from the
// object member — this is the exact shape that fails in the issue's repro.
function StylePropUnionMirrorsRealRepro() {
test('StyleProp union mirrors the real repro', () => {
const s: AnimatedStyle<StyleProp<AugmentedViewStyle>> = {
animationName: { from: { opacity: 0 }, to: { opacity: 1 } },
animationTimingFunction: cubicBezier(0, 0, 0, 0),
};
}
}
});
});
97 changes: 97 additions & 0 deletions packages/react-native-reanimated/__typetests__/miscTest.tst.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import { describe, expect, test } from 'tstyche';

import {
createAnimatedPropAdapter,
Easing,
interpolateColor,
isSharedValue,
Keyframe,
makeMutable,
useAnimatedProps,
useAnimatedStyle,
useSharedValue,
} from '..';

describe('makeMutable', () => {
test('is callable with the initial value', () => {
expect(makeMutable).type.toBeCallableWith(0);
expect(makeMutable).type.toBeCallableWith(true);
});
});

describe('isSharedValue', () => {
test('is callable with any value', () => {
const sv = useSharedValue(0);
expect(isSharedValue).type.toBeCallableWith(null);
expect(isSharedValue).type.toBeCallableWith(undefined);
expect(isSharedValue).type.toBeCallableWith(42);
expect(isSharedValue).type.toBeCallableWith('foo');
expect(isSharedValue).type.toBeCallableWith({ foo: 'bar' });
expect(isSharedValue).type.toBeCallableWith(sv);
});
});

describe('interpolateColor', () => {
test('accepts numeric and string color ranges and an optional color space', () => {
const sv = useSharedValue(0);
expect(interpolateColor).type.toBeCallableWith(
sv.value,
[0, 1],
[0x00ff00, 0x0000ff]
);
expect(interpolateColor).type.toBeCallableWith(
sv.value,
[0, 1],
['red', 'blue']
);
expect(interpolateColor).type.toBeCallableWith(
sv.value,
[0, 1],
['#00FF00', '#0000FF'],
'RGB'
);
expect(interpolateColor).type.toBeCallableWith(
sv.value,
[0, 1],
['#FF0000', '#00FF99'],
'HSV'
);
});
});

describe('animated prop adapters', () => {
test('adapters work with useAnimatedProps but not useAnimatedStyle', () => {
const adapter1 = createAnimatedPropAdapter((_props) => {}, []);
const adapter2 = createAnimatedPropAdapter(
(_props) => {},
['prop1', 'prop2']
);
const adapter3 = createAnimatedPropAdapter(() => {});

expect(useAnimatedProps).type.toBeCallableWith(() => ({}), null, adapter1);
expect(useAnimatedProps).type.toBeCallableWith(() => ({}), null, [
adapter2,
adapter3,
]);
expect(useAnimatedStyle).type.not.toBeCallableWith(() => ({}), undefined, [
adapter1,
adapter2,
adapter3,
]);
});
});

describe('Easing and Keyframe', () => {
test('Easing.bezier is callable', () => {
expect(Easing.bezier).type.toBeCallableWith(0.1, 0.7, 1, 0.1);
});

test('Keyframe is constructable with from/to', () => {
const easing = Easing.bezier(0.1, 0.7, 1, 0.1);
expect(Keyframe).type.toBeConstructableWith({
from: { opacity: 0 },
to: { opacity: 1, easing },
});
});
});
67 changes: 0 additions & 67 deletions packages/react-native-reanimated/__typetests__/miscTest.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, expect, pick, test } from 'tstyche';

import type { DerivedValue } from '..';
import { useDerivedValue, useSharedValue } from '..';

describe('useDerivedValue', () => {
test('infers a DerivedValue of the result type', () => {
const progress = useSharedValue(0);
const derived = useDerivedValue(() => progress.value * 250);
expect(derived).type.toBe<DerivedValue<number>>();
});

test('value is readonly and typed as the result', () => {
const progress = useSharedValue(0);
const width = useDerivedValue(() => progress.value * 250);
expect(pick(width, 'value')).type.toBe<{ readonly value: number }>();
});

test('set is still allowed for now (deprecated)', () => {
const progress = useSharedValue(0);
const width = useDerivedValue(() => progress.value * 250);
// TODO: This should be caught as an illegal operation, since DerivedValue is
// readonly. We can't enforce it yet without breaking DerivedValue ->
// SharedValue assignments, so `set` is marked deprecated and removed later.
width.set(100);
});
});

This file was deleted.

Loading
Loading