mirror of
https://github.com/mattermost/mattermost-mobile.git
synced 2026-11-11 13:40:12 +00:00
* Expo-Router partial migration * Full migration to ExpoRouter * i18n * add tests * Merge branch 'main' into expo-router * align package-lock.json * i18n * feedback review * feedback review * android 16kb patch * Refactored emoji picker keyboard using a state machine * set android launch_screen in drawables instead of layout * no conditional hooks * remove duplicate call in test * address feedback * fix tests and address more feedback * small corrections * small corrections * small corrections * fix draft test * fix draft test 2 * remove navigateToScreen in draff test * fix nit in draft test * remove doc file * Feedback review * fix snapshots * improve test coverage * fix i18n * Upgrade t o RN 0.83, other dependencies and new architecture (#9737) * library hardware-keyboard android new arch support fix * library rnshare android new arch support fix * library rnutils android new arch support fix * library secure-pdf-viewer add new arch support * Enable new arch on iOS and Android * update emm and network client with new-arch * update intune to use new-arch * performance improvements * fix tests * fixes post input on new-arch * fix android emoji picker * remove doc * run tests in parallel * upgrade to React Native 0.83.9, Expo 55, React 19, and New Architecture Bumps core dependencies to their latest versions: - React Native 0.77.3 → 0.83.9 (New Architecture enabled) - React 18.3.1 → 19.2.5 - Expo SDK 52 → 55 - TypeScript 5.8.3 → 6.0.3 - Node.js 22.16.0 → 24.15.0 - react-native-reanimated 3.17.3 → 4.3.0 - Various other dependency upgrades Migrates iOS AppDelegate from Objective-C to Swift, updates Android build tooling and Gradle wrapper, refreshes all patches and font assets, and adapts app code, tests, and CI workflows throughout for API changes introduced by these upgrades. * remove unecessary dismissKeyboard in notifications * feedback review * update intune to use new-arch * update @mattermost deps * missing i18n string * additional fixes * update fastlane
256 lines
10 KiB
TypeScript
256 lines
10 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {Platform} from 'react-native';
|
|
|
|
import {InputContainerStateType, type StateSnapshot, type StateEvent} from '@keyboard/state_machine/types';
|
|
|
|
import {
|
|
isSoftwareKeyboard,
|
|
isZeroHeight,
|
|
calculateAdjustedHeight,
|
|
calculateKeyboardUpdates,
|
|
getEmojiSearchActiveHeight,
|
|
calculateSearchHeight,
|
|
} from './keyboard_utils';
|
|
|
|
jest.mock('@constants/device', () => ({isEdgeToEdge: false}));
|
|
|
|
function makeSnapshot(overrides: Partial<StateSnapshot> = {}): StateSnapshot {
|
|
return {
|
|
inputAccessoryHeight: 0,
|
|
targetHeight: 0,
|
|
postInputTranslateY: 0,
|
|
scrollOffset: 0,
|
|
lastKeyboardHeight: 0,
|
|
keyboardEventHeight: 300,
|
|
preSearchHeight: 0,
|
|
postInputContainerHeight: 0,
|
|
scrollPosition: 0,
|
|
hasZeroKeyboardHeight: false,
|
|
isDraggingKeyboard: false,
|
|
isEmojiPickerTransition: false,
|
|
isEmojiSearchActive: false,
|
|
isWaitingForKeyboard: false,
|
|
currentState: InputContainerStateType.IDLE,
|
|
tabBarHeight: 49,
|
|
safeAreaBottom: 34,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function makeEvent(overrides: Partial<StateEvent> = {}): StateEvent {
|
|
return {
|
|
type: 'KEYBOARD_EVENT_MOVE',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('isSoftwareKeyboard', () => {
|
|
it('should return true when event.height > 75', () => {
|
|
expect(isSoftwareKeyboard(makeSnapshot(), makeEvent({height: 300}))).toBe(true);
|
|
});
|
|
|
|
it('should return false when event.height <= 75', () => {
|
|
expect(isSoftwareKeyboard(makeSnapshot(), makeEvent({height: 30}))).toBe(false);
|
|
});
|
|
|
|
it('should fall back to snapshot.keyboardEventHeight when event.height is undefined', () => {
|
|
expect(isSoftwareKeyboard(makeSnapshot({keyboardEventHeight: 300}), makeEvent())).toBe(true);
|
|
});
|
|
|
|
it('should return false when fallback keyboardEventHeight <= 75', () => {
|
|
expect(isSoftwareKeyboard(makeSnapshot({keyboardEventHeight: 50}), makeEvent())).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isZeroHeight', () => {
|
|
it('should return false immediately when height > 75', () => {
|
|
expect(isZeroHeight(makeSnapshot(), makeEvent({height: 300}))).toBe(false);
|
|
});
|
|
|
|
it('should return true when height < 75', () => {
|
|
expect(isZeroHeight(makeSnapshot(), makeEvent({height: 30}))).toBe(true);
|
|
});
|
|
|
|
it('should return false when height is exactly 75 and hasZeroKeyboardHeight is false', () => {
|
|
expect(isZeroHeight(makeSnapshot({hasZeroKeyboardHeight: false}), makeEvent({height: 75}))).toBe(false);
|
|
});
|
|
|
|
it('should return true when height is exactly 75 and hasZeroKeyboardHeight is true', () => {
|
|
expect(isZeroHeight(makeSnapshot({hasZeroKeyboardHeight: true}), makeEvent({height: 75}))).toBe(true);
|
|
});
|
|
|
|
it('should fall back to snapshot.keyboardEventHeight when event.height is undefined', () => {
|
|
expect(isZeroHeight(makeSnapshot({keyboardEventHeight: 30}), makeEvent())).toBe(true);
|
|
});
|
|
|
|
it('should return false when fallback keyboardEventHeight > 75', () => {
|
|
expect(isZeroHeight(makeSnapshot({keyboardEventHeight: 300}), makeEvent())).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('calculateAdjustedHeight', () => {
|
|
it('should return 0 when rawHeight is 0', () => {
|
|
expect(calculateAdjustedHeight(336, 49, 34, 0)).toBe(0);
|
|
});
|
|
|
|
it('should use rawHeight as targetHeight when lastKeyboardHeight is 0', () => {
|
|
// lastKeyboardHeight=0, rawHeight=300, tabBarHeight=49, safeAreaBottom=0
|
|
// targetHeight=300, keyboardProgress=min(1,300/max(300,336))=300/336
|
|
// tabBarAdjustment=49+0*(300/336)=49
|
|
// result=max(0,300-49)=251
|
|
expect(calculateAdjustedHeight(0, 49, 0, 300)).toBe(251);
|
|
});
|
|
|
|
it('should use lastKeyboardHeight as targetHeight when positive', () => {
|
|
// lastKeyboardHeight=336, rawHeight=200, tabBarHeight=49, safeAreaBottom=34
|
|
// targetHeight=336, keyboardProgress=min(1,200/336)≈0.5952
|
|
// tabBarAdjustment=49+34*0.5952≈49+20.238=69.238
|
|
// result=max(0,200-69.238)≈130.762
|
|
const result = calculateAdjustedHeight(336, 49, 34, 200);
|
|
expect(result).toBeCloseTo(130.76, 1);
|
|
});
|
|
|
|
it('should clamp to 0 when rawHeight is less than tabBarAdjustment', () => {
|
|
// rawHeight=10, tabBarHeight=49, safeAreaBottom=0, lastKeyboardHeight=336
|
|
// keyboardProgress=min(1,10/336)≈0.0298
|
|
// tabBarAdjustment=49+0*0.0298=49
|
|
// result=max(0,10-49)=0
|
|
expect(calculateAdjustedHeight(336, 49, 0, 10)).toBe(0);
|
|
});
|
|
|
|
it('should cap keyboardProgress at 1', () => {
|
|
// rawHeight > targetHeight: rawHeight=400, lastKeyboardHeight=300
|
|
// targetHeight=300, keyboardProgress=min(1,400/max(300,336))=min(1,400/336)=1
|
|
// tabBarAdjustment=49+34*1=83
|
|
// result=max(0,400-83)=317
|
|
expect(calculateAdjustedHeight(300, 49, 34, 400)).toBe(317);
|
|
});
|
|
});
|
|
|
|
describe('calculateKeyboardUpdates', () => {
|
|
const originalOS = Platform.OS;
|
|
|
|
afterEach(() => {
|
|
(Platform as {OS: string}).OS = originalOS;
|
|
});
|
|
|
|
it('should use adjustedHeight when |adjustedHeight - rawKeyboardHeight| > 50', () => {
|
|
(Platform as {OS: string}).OS = 'android';
|
|
const snapshot = makeSnapshot({currentState: InputContainerStateType.IDLE});
|
|
const result = calculateKeyboardUpdates(snapshot, 200, 300);
|
|
expect(result.postInputTranslateY).toEqual({value: 200, animated: false});
|
|
expect(result.scrollOffset).toBeUndefined();
|
|
});
|
|
|
|
it('should include scrollOffset on iOS', () => {
|
|
(Platform as {OS: string}).OS = 'ios';
|
|
const snapshot = makeSnapshot({currentState: InputContainerStateType.IDLE});
|
|
const result = calculateKeyboardUpdates(snapshot, 200, 300);
|
|
expect(result.postInputTranslateY).toEqual({value: 200, animated: false});
|
|
expect(result.scrollOffset).toEqual({value: 200, animated: false});
|
|
});
|
|
|
|
it('should use adjustedHeight for non-keyboard states when no large difference', () => {
|
|
(Platform as {OS: string}).OS = 'android';
|
|
const snapshot = makeSnapshot({currentState: InputContainerStateType.IDLE});
|
|
const result = calculateKeyboardUpdates(snapshot, 250, 260);
|
|
expect(result.postInputTranslateY).toEqual({value: 250, animated: false});
|
|
});
|
|
|
|
it('should return effectiveHeight=0 for KEYBOARD_OPENING when rawHeight <= tabBarAdjustment', () => {
|
|
(Platform as {OS: string}).OS = 'android';
|
|
const snapshot = makeSnapshot({
|
|
currentState: InputContainerStateType.KEYBOARD_OPENING,
|
|
tabBarHeight: 49,
|
|
safeAreaBottom: 34,
|
|
});
|
|
|
|
// tabBarAdjustment = 49 + 34 = 83; rawKeyboardHeight=50 <= 83 → effectiveHeight=0
|
|
const result = calculateKeyboardUpdates(snapshot, 50, 50);
|
|
expect(result.postInputTranslateY).toEqual({value: 0, animated: false});
|
|
});
|
|
|
|
it('should return rawHeight - tabBarAdjustment for KEYBOARD_OPENING when rawHeight > tabBarAdjustment', () => {
|
|
(Platform as {OS: string}).OS = 'android';
|
|
const snapshot = makeSnapshot({
|
|
currentState: InputContainerStateType.KEYBOARD_OPENING,
|
|
tabBarHeight: 49,
|
|
safeAreaBottom: 34,
|
|
});
|
|
|
|
// tabBarAdjustment=83, rawKeyboardHeight=260, adjustedHeight=260 → |260-260|=0 ≤ 50
|
|
// needsConstantSubtraction=true, rawHeight > tabBarAdjustment → effectiveHeight=260-83=177
|
|
const result = calculateKeyboardUpdates(snapshot, 260, 260);
|
|
expect(result.postInputTranslateY).toEqual({value: 177, animated: false});
|
|
});
|
|
|
|
it('should apply constant subtraction for KEYBOARD_OPEN state', () => {
|
|
(Platform as {OS: string}).OS = 'android';
|
|
const snapshot = makeSnapshot({
|
|
currentState: InputContainerStateType.KEYBOARD_OPEN,
|
|
tabBarHeight: 49,
|
|
safeAreaBottom: 34,
|
|
});
|
|
|
|
// tabBarAdjustment=83, rawKeyboardHeight=300, adjustedHeight=300 → |0| ≤ 50
|
|
// needsConstantSubtraction=true → effectiveHeight=300-83=217
|
|
const result = calculateKeyboardUpdates(snapshot, 300, 300);
|
|
expect(result.postInputTranslateY).toEqual({value: 217, animated: false});
|
|
});
|
|
|
|
it('should clamp to 0 when rawHeight - tabBarAdjustment would be negative in KEYBOARD_OPEN', () => {
|
|
(Platform as {OS: string}).OS = 'android';
|
|
const snapshot = makeSnapshot({
|
|
currentState: InputContainerStateType.KEYBOARD_OPEN,
|
|
tabBarHeight: 49,
|
|
safeAreaBottom: 34,
|
|
});
|
|
|
|
// tabBarAdjustment=83, rawKeyboardHeight=50 → 50 <= 83 → effectiveHeight=0
|
|
const result = calculateKeyboardUpdates(snapshot, 50, 50);
|
|
expect(result.postInputTranslateY).toEqual({value: 0, animated: false});
|
|
});
|
|
|
|
it('should use adjustedHeight for EMOJI_PICKER_OPEN state (no constant subtraction)', () => {
|
|
(Platform as {OS: string}).OS = 'android';
|
|
const snapshot = makeSnapshot({
|
|
currentState: InputContainerStateType.EMOJI_PICKER_OPEN,
|
|
tabBarHeight: 49,
|
|
safeAreaBottom: 34,
|
|
});
|
|
|
|
// adjustedHeight=250, rawKeyboardHeight=260 → |250-260|=10 ≤ 50
|
|
// needsConstantSubtraction=false → effectiveHeight=adjustedHeight=250
|
|
const result = calculateKeyboardUpdates(snapshot, 250, 260);
|
|
expect(result.postInputTranslateY).toEqual({value: 250, animated: false});
|
|
});
|
|
});
|
|
|
|
describe('getEmojiSearchActiveHeight', () => {
|
|
it('should compute height without edge-to-edge using SEARCH_CONTAINER_PADDING as offset', () => {
|
|
// offset=8 → (56+8+8+50+49)-34 = 171-34 = 137
|
|
expect(getEmojiSearchActiveHeight(49, 34)).toBe(137);
|
|
});
|
|
|
|
it('should handle zero tabBarHeight and safeAreaBottom', () => {
|
|
// offset=8 → (56+8+8+50+0)-0 = 122
|
|
expect(getEmojiSearchActiveHeight(0, 0)).toBe(122);
|
|
});
|
|
});
|
|
|
|
describe('calculateSearchHeight', () => {
|
|
it('should return keyboardHeight + getEmojiSearchActiveHeight', () => {
|
|
// getEmojiSearchActiveHeight(49, 34) = 137 (non-edge-to-edge)
|
|
// keyboardHeight=336 → 336 + 137 = 473
|
|
expect(calculateSearchHeight(336, 49, 34)).toBe(473);
|
|
});
|
|
|
|
it('should work with zero keyboard height', () => {
|
|
// getEmojiSearchActiveHeight(0, 0) = 122
|
|
// keyboardHeight=0 → 0 + 122 = 122
|
|
expect(calculateSearchHeight(0, 0, 0)).toBe(122);
|
|
});
|
|
});
|