Skip to content
Merged
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
137 changes: 120 additions & 17 deletions example/react-native/__tests__/CollapsiblePagerView.web.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,15 @@ describe('CollapsiblePagerView native wrapper', () => {
const pressTab = async (
animationEnabled: boolean | undefined,
position: number,
initialPage = 0,
) => {
let renderer!: ReactTestRenderer.ReactTestRenderer;
await ReactTestRenderer.act(() => {
renderer = ReactTestRenderer.create(
<NativeCollapsiblePagerView
{...requiredProps}
nativeTabBar={nativeTabBar}
initialPage={initialPage}
nativeTabPressAnimationEnabled={animationEnabled}
>
{pages(nativeTabBar.items.length)}
Expand All @@ -141,29 +143,130 @@ describe('CollapsiblePagerView native wrapper', () => {
});
};

it('animates the page change by default', async () => {
await pressTab(undefined, 3);
it.each([
[undefined, 0, 1, true],
[undefined, 0, 3, false],
[undefined, 3, 2, true],
[undefined, 3, 0, false],
[true, 0, 3, true],
[false, 0, 1, false],
[false, 0, 3, false],
] as const)(
'uses animation=%s from %s to %s with animated command=%s',
async (animationEnabled, initialPage, target, animated) => {
await pressTab(animationEnabled, target, initialPage);
const command = animated
? NativeCollapsiblePagerCommands.setPage
: NativeCollapsiblePagerCommands.setPageWithoutAnimation;
const otherCommand = animated
? NativeCollapsiblePagerCommands.setPageWithoutAnimation
: NativeCollapsiblePagerCommands.setPage;
expect(command).toHaveBeenCalledTimes(1);
expect(command).toHaveBeenCalledWith(expect.anything(), target);
expect(otherCommand).not.toHaveBeenCalled();
},
);

expect(NativeCollapsiblePagerCommands.setPage).toHaveBeenCalledTimes(1);
expect(NativeCollapsiblePagerCommands.setPage).toHaveBeenCalledWith(
expect.anything(),
3,
const renderTabPager = async () => {
let renderer!: ReactTestRenderer.ReactTestRenderer;
await ReactTestRenderer.act(() => {
renderer = ReactTestRenderer.create(
<NativeCollapsiblePagerView
{...requiredProps}
nativeTabBar={nativeTabBar}
>
{pages(nativeTabBar.items.length)}
</NativeCollapsiblePagerView>,
{ createNodeMock: () => ({}) },
);
});
const nativeHost = renderer.root.find(
node => typeof node.props.retainedPages === 'string',
);
expect(
const press = async (position: number, runFrame = true) => {
await ReactTestRenderer.act(() => {
nativeHost.props.onNativeTabPress({
nativeEvent: { position, key: nativeTabBar.items[position].key },
});
});
if (runFrame) {
await ReactTestRenderer.act(() => {
jest.runOnlyPendingTimers();
});
}
};
const select = async (position: number) => {
await ReactTestRenderer.act(() => {
nativeHost.props.onPageSelected({ nativeEvent: { position } });
});
};
return { renderer, press, select };
};

const lastCommand = () => {
const animated = jest.mocked(NativeCollapsiblePagerCommands.setPage).mock
.invocationCallOrder;
const jumped = jest.mocked(
NativeCollapsiblePagerCommands.setPageWithoutAnimation,
).not.toHaveBeenCalled();
});
).mock.invocationCallOrder;
const lastAnimated = Math.max(-1, ...animated);
const lastJump = Math.max(-1, ...jumped);
return lastAnimated > lastJump
? {
animated: true,
target: jest.mocked(NativeCollapsiblePagerCommands.setPage).mock
.lastCall?.[1],
}
: {
animated: false,
target: jest.mocked(
NativeCollapsiblePagerCommands.setPageWithoutAnimation,
).mock.lastCall?.[1],
};
};

it('jumps straight to the pressed page when animation is disabled', async () => {
await pressTab(false, 3);
it.each([
[2, true],
[0, false],
[1, false],
] as const)(
'measures adjacency from an in-flight jump target when pressing %s',
async (target, animated) => {
const { renderer, press } = await renderTabPager();
await press(3);
expect(lastCommand()).toEqual({ animated: false, target: 3 });
// Native has not reported page 3 yet; the wrapper state still says 0.
await press(target);
expect(lastCommand()).toEqual({ animated, target });
await ReactTestRenderer.act(() => {
renderer.unmount();
});
},
);

expect(
NativeCollapsiblePagerCommands.setPageWithoutAnimation,
).toHaveBeenCalledTimes(1);
expect(
NativeCollapsiblePagerCommands.setPageWithoutAnimation,
).toHaveBeenCalledWith(expect.anything(), 3);
it('ignores a superseded target that never reached native', async () => {
const { renderer, press } = await renderTabPager();
await press(3, false);
await press(2);
expect(NativeCollapsiblePagerCommands.setPage).not.toHaveBeenCalled();
expect(lastCommand()).toEqual({ animated: false, target: 2 });
await ReactTestRenderer.act(() => {
renderer.unmount();
});
});

it('measures adjacency from the reported page after the transition', async () => {
const { renderer, press, select } = await renderTabPager();
await press(3);
await select(3);
await press(1);
expect(lastCommand()).toEqual({ animated: false, target: 1 });
await select(1);
await press(0);
expect(lastCommand()).toEqual({ animated: true, target: 0 });
await ReactTestRenderer.act(() => {
renderer.unmount();
});
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion example/react-native/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3527,7 +3527,7 @@ SPEC CHECKSUMS:
React-microtasksnativemodule: f413ee411341fb3c34d20e85e9f6f524921fab15
React-mutationobservernativemodule: 58f6d2adf7d98e72b241608dfa2ddce414d2a4e7
react-native-native-list: c586598e9a4902586b091b060d8b594013dc442a
react-native-pager-view: c41389cfc805ce60b5980924a480f982ae261e7f
react-native-pager-view: d0165b7fba601f2ce11f4798622122002562c82c
react-native-safe-area-context: 91a90d98c310adcc90a511e5aeb6046d7c19d885
react-native-tab-view: b62691e2b0492c55dc436cf2f971c27f4a0165cc
React-NativeModulesApple: 167e532fb9bae30f3c66636b8ee0092bab263667
Expand Down
7 changes: 7 additions & 0 deletions native-views/react-native-native-list/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,18 @@ repositories {
}

def kotlinVersion = getExtOrDefault("kotlinVersion")
// Keep native media preview formats aligned with react-native-video's default.
def media3Version = rootProject.ext.has("media3Version") ? rootProject.ext.get("media3Version") : "1.4.1"

dependencies {
implementation "com.facebook.react:react-android"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation "androidx.recyclerview:recyclerview:1.4.0"
implementation "androidx.media3:media3-exoplayer:$media3Version"
implementation "androidx.media3:media3-exoplayer-hls:$media3Version"
implementation "androidx.media3:media3-exoplayer-dash:$media3Version"
implementation "androidx.media3:media3-ui:$media3Version"
implementation "androidx.media3:media3-datasource-okhttp:$media3Version"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation project(":react-native-nitro-modules")
implementation project(":onekeyfe_react-native-native-logger")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ class HybridNativeList(context: ThemedReactContext) : HybridNativeListSpec() {

override val view: View = hostView

override var scrollPositionThresholdsJson: String = ""
set(value) {
if (field == value) return
field = value
dispatchToUi { hostView.setScrollPositionThresholdsJson(value) }
}

override var onScrollPositionThresholdChange: ((isBeyondThreshold: Boolean) -> Unit)? = null
set(value) {
field = value
dispatchToUi { hostView.onScrollPositionThresholdChange = value }
}

override var containerSlotHeightsJson: String = "[]"
set(value) {
if (field == value) return
field = value
dispatchToUi { hostView.setContainerSlotHeightsJson(value) }
}

override var snapshotJson: String = ""
get() = field
set(value) {
Expand Down
Loading
Loading