Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ function KeyboardChatScrollViewPlayground() {
const flashRef = useRef<FlashListRef<MessageProps>>(null);
const flatRef = useRef<FlatList<MessageProps>>(null);
const scrollRef = useRef<VirtualizedListScrollViewRef>(null);
const textInputRef = useRef<TextInput>(null);
const textRef = useRef("");
const [text, setText] = useState("");
const [inputHeight, setInputHeight] = useState(TEXT_INPUT_HEIGHT);
const extraContentPadding = useSharedValue(0);
const { inverted, messages, reversedMessages, addMessage, mode } =
Expand All @@ -70,20 +69,19 @@ function KeyboardChatScrollViewPlayground() {
},
[extraContentPadding],
);
const onInput = useCallback((text: string) => {
textRef.current = text;
const onInput = useCallback((value: string) => {
setText(value);
}, []);
const onSend = useCallback(() => {
const message = textRef.current.trim();
const message = text.trim();

if (message === "") {
return;
}

addMessage({ text: message, sender: true });
textInputRef.current?.clear();
textRef.current = "";
}, [addMessage]);
setText("");
}, [addMessage, text]);

useEffect(() => {
legendRef.current?.scrollToOffset({
Expand Down Expand Up @@ -177,11 +175,11 @@ function KeyboardChatScrollViewPlayground() {
/>
</View>
<TextInput
ref={textInputRef}
multiline
nativeID="chat-input"
style={styles.input}
testID="chat.input"
value={text}
onChangeText={onInput}
onLayout={onInputLayoutChanged}
/>
Expand Down
1 change: 1 addition & 0 deletions src/architecture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const IS_FABRIC = "nativeFabricUIManager" in global;
1 change: 1 addition & 0 deletions src/components/KeyboardChatScrollView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const KeyboardChatScrollView = forwardRef<
scroll,
layout,
size,
contentOffsetY,
inverted,
keyboardLiftBehavior,
freeze,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { renderHook } from "@testing-library/react-native";
import { useAnimatedRef } from "react-native-reanimated";

import { useExtraContentPadding } from "..";
import { sv } from "../../../../__fixtures__/sv";

import type { useExtraContentPadding } from "..";
import type { SharedValue } from "react-native-reanimated";
import type Reanimated from "react-native-reanimated";

export const mockScrollTo = jest.fn();
export let reactionEffect: (current: number, previous: number | null) => void;

export const flushRAF = () => new Promise((resolve) => setTimeout(resolve, 0));

let mockForceLegacy = false;

jest.mock("../../../../architecture", () => ({
get IS_FABRIC() {
return !mockForceLegacy;
},
}));

jest.mock("react-native-reanimated", () => ({
...require("react-native-reanimated/mock"),
scrollTo: (...args: unknown[]) => mockScrollTo(...args),
Expand All @@ -31,15 +41,10 @@ type RenderOptions = Omit<

export const createRender = () => {
return function render(options: RenderOptions) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const mod = require("..") as {
useExtraContentPadding: typeof useExtraContentPadding;
};

return renderHook(() => {
const ref = useAnimatedRef<Reanimated.ScrollView>();

mod.useExtraContentPadding({
useExtraContentPadding({
scrollViewRef: ref,
blankSpace: options.blankSpace ?? sv(0),
...options,
Expand All @@ -49,6 +54,11 @@ export const createRender = () => {
};

beforeEach(() => {
jest.resetModules();
mockForceLegacy = false;
mockScrollTo.mockClear();
});

export const withLegacyArch = (fn: () => void) => {
mockForceLegacy = true;
fn();
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { sv } from "../../../../__fixtures__/sv";
import {
createRender,
flushRAF,
mockScrollTo,
reactionEffect,
} from "../__fixtures__/setup";

describe("useExtraContentPadding — always behavior", () => {
it("should scrollTo on grow when at end (non-inverted)", () => {
it("should scrollTo on grow when at end (non-inverted)", async () => {
const render = createRender();

render({
Expand All @@ -21,6 +22,7 @@ describe("useExtraContentPadding — always behavior", () => {
});

reactionEffect(20, 0);
await flushRAF();

expect(mockScrollTo).toHaveBeenCalledWith(
expect.anything(),
Expand All @@ -30,7 +32,7 @@ describe("useExtraContentPadding — always behavior", () => {
);
});

it("should scrollTo on grow when NOT at end (non-inverted)", () => {
it("should scrollTo on grow when NOT at end (non-inverted)", async () => {
const render = createRender();

render({
Expand All @@ -45,11 +47,12 @@ describe("useExtraContentPadding — always behavior", () => {
});

reactionEffect(20, 0);
await flushRAF();

expect(mockScrollTo).toHaveBeenCalledWith(expect.anything(), 0, 120, false);
});

it("should scrollTo on shrink (non-inverted)", () => {
it("should scrollTo on shrink (non-inverted)", async () => {
const render = createRender();

render({
Expand All @@ -64,6 +67,7 @@ describe("useExtraContentPadding — always behavior", () => {
});

reactionEffect(0, 20);
await flushRAF();

expect(mockScrollTo).toHaveBeenCalledWith(
expect.anything(),
Expand All @@ -73,7 +77,7 @@ describe("useExtraContentPadding — always behavior", () => {
);
});

it("should scrollTo on grow (inverted)", () => {
it("should scrollTo on grow (inverted)", async () => {
const render = createRender();

render({
Expand All @@ -88,6 +92,7 @@ describe("useExtraContentPadding — always behavior", () => {
});

reactionEffect(20, 0);
await flushRAF();

expect(mockScrollTo).toHaveBeenCalledWith(expect.anything(), 0, -15, false);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { sv } from "../../../../__fixtures__/sv";
import {
createRender,
flushRAF,
mockScrollTo,
reactionEffect,
} from "../__fixtures__/setup";
Expand Down Expand Up @@ -29,7 +30,7 @@ describe("useExtraContentPadding — blankSpace floor", () => {
expect(mockScrollTo).not.toHaveBeenCalled();
});

it("should scroll by effective delta when blankSpace partially absorbs", () => {
it("should scroll by effective delta when blankSpace partially absorbs", async () => {
const render = createRender();

render({
Expand All @@ -50,11 +51,12 @@ describe("useExtraContentPadding — blankSpace floor", () => {
// maxScroll = max(2000 - 800 + 500, 0) = 1700
// target = min(100 + 100, 1700) = 200
reactionEffect(300, 0);
await flushRAF();

expect(mockScrollTo).toHaveBeenCalledWith(expect.anything(), 0, 200, false);
});

it("blankSpace=0 produces identical behavior to default", () => {
it("blankSpace=0 produces identical behavior to default", async () => {
const render = createRender();

render({
Expand All @@ -75,6 +77,7 @@ describe("useExtraContentPadding — blankSpace floor", () => {
// maxScroll = max(2000 - 800 + 320, 0) = 1520
// target = min(1200 + 20, 1520) = 1220
reactionEffect(20, 0);
await flushRAF();

expect(mockScrollTo).toHaveBeenCalledWith(
expect.anything(),
Expand Down Expand Up @@ -107,7 +110,7 @@ describe("useExtraContentPadding — blankSpace floor", () => {
expect(mockScrollTo).not.toHaveBeenCalled();
});

it("should scroll when change exceeds blankSpace floor (inverted)", () => {
it("should scroll when change exceeds blankSpace floor (inverted)", async () => {
const render = createRender();

render({
Expand All @@ -127,6 +130,7 @@ describe("useExtraContentPadding — blankSpace floor", () => {
// effectiveDelta = 100
// target = max(5 - 100, -500) = max(-95, -500) = -95
reactionEffect(200, 0);
await flushRAF();

expect(mockScrollTo).toHaveBeenCalledWith(expect.anything(), 0, -95, false);
});
Expand Down
Loading
Loading