diff --git a/shared/chat/conversation/input-area/suggestors/commands.tsx b/shared/chat/conversation/input-area/suggestors/commands.tsx index 2e6d207c2aa0..5f40d5efb419 100644 --- a/shared/chat/conversation/input-area/suggestors/commands.tsx +++ b/shared/chat/conversation/input-area/suggestors/commands.tsx @@ -237,8 +237,8 @@ export const List = (p: ListProps) => { const {botCommands} = useConversationMeta(conversationIDKey) const items = useDataSource({conversationIDKey, filter, inputSnapshot, suppressCommandSuggestions}) return ( - - + + { loading={false} rowHeight={rowHeight} /> - - + + ) } diff --git a/shared/chat/conversation/list-area/catch-up.test.tsx b/shared/chat/conversation/list-area/catch-up.test.tsx new file mode 100644 index 000000000000..f0bf5dcd1587 --- /dev/null +++ b/shared/chat/conversation/list-area/catch-up.test.tsx @@ -0,0 +1,138 @@ +/** @jest-environment jsdom */ +/// +import * as React from 'react' +import * as T from '@/constants/types' +import {act, cleanup, fireEvent, render, screen} from '@testing-library/react' +import {OrangeLineContext} from '../orange-line-context' +import {CatchUp, shouldShowCatchUp, useCatchUp} from './catch-up' + +const ord = T.Chat.numberToOrdinal + +// The orange line sits at ordinal 10, the viewport starts at 50: the unread boundary is off +// screen above. +const scrolledPastTheOrangeLine = { + dismissedOrdinal: ord(0), + loaded: true, + oldestVisibleOrdinal: ord(50), + orangeLineOrdinal: ord(10), + threadSearchVisible: false, +} + +test('shows when the orange line is older than the oldest visible message', () => { + expect(shouldShowCatchUp(scrolledPastTheOrangeLine)).toBe(true) +}) + +test('hides when there is no orange line at all', () => { + expect(shouldShowCatchUp({...scrolledPastTheOrangeLine, orangeLineOrdinal: ord(0)})).toBe(false) +}) + +test('hides when the orange line is the oldest visible message', () => { + expect(shouldShowCatchUp({...scrolledPastTheOrangeLine, orangeLineOrdinal: ord(50)})).toBe(false) +}) + +test('hides before the list has reported what it can see', () => { + expect(shouldShowCatchUp({...scrolledPastTheOrangeLine, oldestVisibleOrdinal: undefined})).toBe(false) +}) + +test('hides until the thread has loaded', () => { + expect(shouldShowCatchUp({...scrolledPastTheOrangeLine, loaded: false})).toBe(false) +}) + +test('hides while thread search is open', () => { + expect(shouldShowCatchUp({...scrolledPastTheOrangeLine, threadSearchVisible: true})).toBe(false) +}) + +test('stays hidden once dismissed for this orange line', () => { + expect(shouldShowCatchUp({...scrolledPastTheOrangeLine, dismissedOrdinal: ord(10)})).toBe(false) +}) + +test('comes back when a newer orange line replaces the dismissed one', () => { + expect( + shouldShowCatchUp({...scrolledPastTheOrangeLine, dismissedOrdinal: ord(10), orangeLineOrdinal: ord(20)}) + ).toBe(true) +}) + +const mockCenterOnMessage = jest.fn() +let mockRouteParams: {threadSearch?: {query?: string}} | undefined + +jest.mock('../center-context', () => ({ + useConversationCenterActions: () => ({centerOnMessage: mockCenterOnMessage}), +})) +jest.mock('../thread-search-route', () => ({useChatThreadRouteParams: () => mockRouteParams})) + +let seen: ReturnType | undefined + +const Probe = (p: {loaded: boolean}) => { + const catchUp = useCatchUp({loaded: p.loaded}) + // captured in an effect: assigning module state during render is a side effect the lint rejects + React.useEffect(() => { + seen = catchUp + }) + return null +} + +const Tree = (p: {loaded?: boolean; orangeLineOrdinal: T.Chat.Ordinal}) => ( + + + +) + +describe('useCatchUp', () => { + beforeEach(() => { + mockCenterOnMessage.mockClear() + mockRouteParams = undefined + seen = undefined + }) + afterEach(cleanup) + + test('stays hidden until the list reports a viewport above the orange line', () => { + render() + expect(seen?.showCatchUp).toBe(false) + act(() => { + seen?.onViewableOrdinalsChanged(ord(50)) + }) + expect(seen?.showCatchUp).toBe(true) + }) + + test('tapping centers on the orange line with no highlight', () => { + render() + act(() => { + seen?.onViewableOrdinalsChanged(ord(50)) + }) + act(() => { + seen?.onCatchUp() + }) + expect(mockCenterOnMessage).toHaveBeenCalledWith(T.Chat.numberToMessageID(10), 'none') + }) + + test('tapping dismisses the pill even though the viewport has not moved', () => { + render() + act(() => { + seen?.onViewableOrdinalsChanged(ord(50)) + }) + act(() => { + seen?.onCatchUp() + }) + expect(seen?.showCatchUp).toBe(false) + }) +}) + +// The pill is a ClickableBox, which is a bare div by default: without button semantics it is not a +// tab stop and enter/space do nothing, so keyboard users have no way to reach the unread line. +describe('CatchUp', () => { + afterEach(cleanup) + + test('is reachable from the keyboard', () => { + render() + expect(screen.getByRole('button')).toHaveProperty('tabIndex', 0) + }) + + test('activates on enter and on space', () => { + const onClick = jest.fn() + render() + const pill = screen.getByRole('button') + fireEvent.keyDown(pill, {key: 'Enter'}) + fireEvent.keyDown(pill, {key: ' '}) + expect(onClick).toHaveBeenCalledTimes(2) + }) +}) diff --git a/shared/chat/conversation/list-area/catch-up.tsx b/shared/chat/conversation/list-area/catch-up.tsx new file mode 100644 index 000000000000..fd2ac7d55ac1 --- /dev/null +++ b/shared/chat/conversation/list-area/catch-up.tsx @@ -0,0 +1,127 @@ +import * as Kb from '@/common-adapters' +import * as React from 'react' +import * as T from '@/constants/types' +import {OrangeLineContext} from '../orange-line-context' +import {useChatThreadRouteParams} from '../thread-search-route' +import {useConversationCenterActions} from '../center-context' + +const noOrdinal = T.Chat.numberToOrdinal(0) + +// The unreadline arrives as a MessageID and is carried as an Ordinal, which is sound because server +// messages get ordinal === messageID. Centering wants it back as a MessageID. +const orangeLineToMessageID = (ordinal: T.Chat.Ordinal) => + T.Chat.numberToMessageID(T.Chat.ordinalToNumber(ordinal)) + +// Ordinals are monotonic and the orange line is a MessageID coerced to one, so a plain comparison +// tells us the unread boundary is above the viewport even when that message isn't loaded at all. +export const shouldShowCatchUp = (p: { + dismissedOrdinal: T.Chat.Ordinal + loaded: boolean + oldestVisibleOrdinal: T.Chat.Ordinal | undefined + orangeLineOrdinal: T.Chat.Ordinal + threadSearchVisible: boolean +}) => { + const {dismissedOrdinal, loaded, oldestVisibleOrdinal, orangeLineOrdinal, threadSearchVisible} = p + if (!loaded || threadSearchVisible) { + return false + } + if (oldestVisibleOrdinal === undefined || !T.Chat.ordinalToNumber(orangeLineOrdinal)) { + return false + } + // Dismissal is remembered per orange line, not per visit, so marking an older message unread + // re-arms the pill for the new boundary. + if (dismissedOrdinal === orangeLineOrdinal) { + return false + } + return orangeLineOrdinal < oldestVisibleOrdinal +} + +// The viewport moves on every scroll frame, so the viewable ordinal lives in a ref and only the +// show/hide answer is state: an unchanged answer bails out of re-rendering the message list. +export const useCatchUp = (p: {loaded: boolean}) => { + const {loaded} = p + const orangeLineOrdinal = React.useContext(OrangeLineContext) + const routeParams = useChatThreadRouteParams() + const threadSearchVisible = !!routeParams?.threadSearch + const {centerOnMessage} = useConversationCenterActions() + const [dismissedOrdinal, setDismissedOrdinal] = React.useState(noOrdinal) + const [showCatchUp, setShowCatchUp] = React.useState(false) + const oldestVisibleOrdinalRef = React.useRef(undefined) + + const recompute = React.useEffectEvent(() => { + setShowCatchUp( + shouldShowCatchUp({ + dismissedOrdinal, + loaded, + oldestVisibleOrdinal: oldestVisibleOrdinalRef.current, + orangeLineOrdinal, + threadSearchVisible, + }) + ) + }) + + React.useEffect(() => { + recompute() + }, [dismissedOrdinal, loaded, orangeLineOrdinal, threadSearchVisible]) + + // Held in state rather than a useCallback so the identity is stable for the lists, which capture + // this once inside their own scroll handlers. + const [onViewableOrdinalsChanged] = React.useState( + () => (oldestVisibleOrdinal?: T.Chat.Ordinal) => { + oldestVisibleOrdinalRef.current = oldestVisibleOrdinal + recompute() + } + ) + + const onCatchUp = React.useCallback(() => { + setDismissedOrdinal(orangeLineOrdinal) + setShowCatchUp(false) + centerOnMessage(orangeLineToMessageID(orangeLineOrdinal), 'none') + }, [centerOnMessage, orangeLineOrdinal]) + + return {onCatchUp, onViewableOrdinalsChanged, showCatchUp} +} + +// Orange to match the unread line itself, so the pill reads as "that line, up there". +export const CatchUp = (p: {onClick: () => void}) => { + const {onClick} = p + const styles = useStyles() + const theme = Kb.Styles.useTheme() + return ( + + + + + Catch up + + + + ) +} + +const useStyles = Kb.Styles.createStyleHook( + theme => + ({ + container: { + position: 'absolute', + right: Kb.Styles.globalMargins.tiny, + top: Kb.Styles.globalMargins.tiny, + }, + label: {color: theme.whiteOrWhite}, + pill: { + backgroundColor: theme.orange, + borderRadius: 100, + paddingBottom: Kb.Styles.globalMargins.xtiny, + paddingLeft: Kb.Styles.globalMargins.tiny, + paddingRight: Kb.Styles.globalMargins.tiny, + paddingTop: Kb.Styles.globalMargins.xtiny, + }, + }) as const +) diff --git a/shared/chat/conversation/list-area/index.tsx b/shared/chat/conversation/list-area/index.tsx index 7800f4eb543d..cd67d88f843b 100644 --- a/shared/chat/conversation/list-area/index.tsx +++ b/shared/chat/conversation/list-area/index.tsx @@ -3,7 +3,7 @@ import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' import * as TestIDs from '@/tests/e2e/shared/test-ids' -import Separator from '../messages/separator' +import Separator, {NativeSeparator} from '../messages/separator' import SpecialBottomMessage from '../messages/special-bottom-message' import SpecialTopMessage from '../messages/special-top-message' import {MessageRow} from '../messages/wrapper' @@ -20,6 +20,7 @@ import { useConversationThreadSelector, useConversationThreadStore, } from '../thread-context' +import {CatchUp, useCatchUp} from './catch-up' import {useJumpToRecent} from './jump-to-recent' import {useThreadLoadStatusOptionsGetter} from '../thread-load-status-context' import {getMessageRowType, getMessageShowUsername} from '../messages/row-metadata' @@ -538,6 +539,15 @@ const DesktopThreadWrapper = function DesktopThreadWrapper() { const jumpToRecent = useJumpToRecent(scrollToBottom, messageOrdinals.length) + const {onCatchUp, onViewableOrdinalsChanged, showCatchUp} = useCatchUp({loaded}) + // Data runs oldest-first here, so the first viewable row is the oldest one on screen. + const onViewableItemsChanged = React.useCallback( + (info: {viewableItems: ReadonlyArray<{item: T.Chat.Ordinal}>}) => { + onViewableOrdinalsChanged(info.viewableItems.at(0)?.item) + }, + [onViewableOrdinalsChanged] + ) + const {focusInput} = React.useContext(ThreadRefsContext) const handleListClick = (ev: React.MouseEvent) => { const target = ev.target as { @@ -657,8 +667,10 @@ const DesktopThreadWrapper = function DesktopThreadWrapper() { onStartReached={onStartReached} onStartReachedThreshold={2} onEndReached={onEndReached} + onViewableItemsChanged={onViewableItemsChanged} /> {jumpToRecent} + {showCatchUp && } ) @@ -947,6 +959,8 @@ const NativeConversationList = function NativeConversationList() { const jumpToRecent = useJumpToRecent(scrollToBottom, messageOrdinals.length) + const {onCatchUp, onViewableOrdinalsChanged, showCatchUp} = useCatchUp({loaded}) + // When keyboard is open, maintainVisibleContentPosition adjusts contentOffset by the new // message height when a message is added, undoing the scrollToBottom from onSubmit. // Defer the re-scroll past the native MPV adjustment (which runs on the UI thread after @@ -1040,13 +1054,15 @@ const NativeConversationList = function NativeConversationList() { const onViewableItemsChanged = useNativeSafeOnViewableItemsChanged(onEndReached, messageOrdinals.length) const [onViewableItemsChangedNative] = React.useState( - () => (info: {viewableItems: Array<{index: number | null}>}) => { + () => (info: {viewableItems: Array<{index: number | null; item: T.Chat.Ordinal}>}) => { onViewableItemsChanged.current(info) const first = info.viewableItems.at(0)?.index const last = info.viewableItems.at(-1)?.index vFirstRef.current = first vLastRef.current = last correctCenter(first, last) + // The list is inverted and its data reversed, so the last viewable row is the oldest. + onViewableOrdinalsChanged(info.viewableItems.at(-1)?.item) } ) @@ -1086,7 +1102,7 @@ const NativeConversationList = function NativeConversationList() { estimatedItemSize={72} ListHeaderComponent={SpecialBottomMessage} ListFooterComponent={SpecialTopMessage} - ItemSeparatorComponent={Separator} + ItemSeparatorComponent={NativeSeparator} overScrollMode="never" contentContainerStyle={nativeContentContainerStyle} data={messageOrdinals} @@ -1115,6 +1131,7 @@ const NativeConversationList = function NativeConversationList() { {jumpToRecent} )} + {showCatchUp && } diff --git a/shared/chat/conversation/messages/separator.test.tsx b/shared/chat/conversation/messages/separator.test.tsx new file mode 100644 index 000000000000..616fe29a430e --- /dev/null +++ b/shared/chat/conversation/messages/separator.test.tsx @@ -0,0 +1,75 @@ +/** @jest-environment jsdom */ +/// +import type * as React from 'react' +import * as T from '@/constants/types' +import * as Chat from '@/constants/chat' +import {cleanup, render} from '@testing-library/react' +import {OrangeLineContext} from '../orange-line-context' + +const ord = T.Chat.numberToOrdinal +const older = ord(10) +const newer = ord(20) + +// The thread the separator reads: two messages, the unread boundary sitting between them, so the +// separator above `newer` is the one that must draw the orange line. +type SeparatorData = {orangeLineAbove: boolean; orangeTime: string; ordinal: T.Chat.Ordinal} +let mockThreadState: unknown + +jest.mock('../thread-context', () => ({ + // read via useContext but only consulted for the desktop timestamp label, which these cases + // don't exercise + ShownUsernameCacheContext: {}, + useConversationThreadSelector: (sel: (s: unknown) => SeparatorData): SeparatorData => + sel(mockThreadState), +})) +jest.mock('@/stores/current-user', () => ({useCurrentUserState: () => 'testuser'})) + +import Separator, {NativeSeparator} from './separator' + +const Tree = (p: {children: React.ReactNode}) => ( + {p.children} +) + +// The line is a 1px bar painted with the orange theme token, so its presence is what proves the +// separator drew an unread boundary rather than merely rendering something. +const drewOrangeLine = (container: HTMLElement) => container.innerHTML.includes('var(--color-orange)') + +beforeEach(() => { + mockThreadState = { + messageMap: new Map([ + [older, Chat.makeMessageText({ordinal: older})], + [newer, Chat.makeMessageText({ordinal: newer})], + ]), + messageOrdinals: [older, newer], + } +}) +afterEach(cleanup) + +test('draws the orange line for the message below the unread boundary', () => { + const {container} = render( + + + + ) + expect(drewOrangeLine(container)).toBe(true) +}) + +// react-native's VirtualizedListCellRenderer only ever passes {highlighted, leadingItem} to +// ItemSeparatorComponent -- there is no trailingItem on native. +test('draws the orange line when the list supplies react-native separator props', () => { + const {container} = render( + + + + ) + expect(drewOrangeLine(container)).toBe(true) +}) + +test('leaves the older message of the pair alone', () => { + const {container} = render( + + + + ) + expect(drewOrangeLine(container)).toBe(false) +}) diff --git a/shared/chat/conversation/messages/separator.tsx b/shared/chat/conversation/messages/separator.tsx index 56315c22b81a..81366147e1b8 100644 --- a/shared/chat/conversation/messages/separator.tsx +++ b/shared/chat/conversation/messages/separator.tsx @@ -124,4 +124,13 @@ const useStyles = Kb.Styles.createStyleHook( }) as const ) +// react-native hands ItemSeparatorComponent {highlighted, leadingItem} and nothing else -- there is +// no trailingItem on native. The separator belongs to cell i and sits between items i and i+1, and +// the thread list reverses its data, so item i is the newer of the pair: exactly the message +// desktop passes as trailingItem. +export const NativeSeparator = (p: {leadingItem?: T.Chat.Ordinal}) => { + const {leadingItem} = p + return leadingItem === undefined ? null : +} + export default SeparatorConnector diff --git a/shared/chat/conversation/team-hooks.tsx b/shared/chat/conversation/team-hooks.tsx index 94d264f5709b..ac47d161ac5c 100644 --- a/shared/chat/conversation/team-hooks.tsx +++ b/shared/chat/conversation/team-hooks.tsx @@ -202,7 +202,7 @@ export const ChatTeamProvider = (props: React.PropsWithChildren) => { sameAsOuter ) const value: ChatTeamContextValue = sameAsOuter ? outer! : {members, team, teamID} - return {children} + return {children} } export const useChatTeam = (teamID: T.Teams.TeamID, teamname?: string): ChatTeam => { diff --git a/shared/common-adapters/box.tsx b/shared/common-adapters/box.tsx index 438b4286b0f6..a9019f17a499 100644 --- a/shared/common-adapters/box.tsx +++ b/shared/common-adapters/box.tsx @@ -313,15 +313,28 @@ export type ClickableBoxProps = Box2Props & { onClick?: (e?: React.BaseSyntheticEvent) => void onLongPress?: () => void hitSlop?: number + // Opt-in button semantics: a tab stop on desktop, activated with enter/space, announced as a + // button. Off by default because most clickable boxes wrap rows and whole cards, and making + // every one of them a tab stop would bury the real controls. + asButton?: boolean } export const ClickableBox = (p: ClickableBoxProps & {ref?: React.Ref}) => { - const {onClick, onLongPress, hitSlop, ref, ...box2p} = p + const {onClick, onLongPress, hitSlop, asButton, ref, ...box2p} = p if (!isMobile) { const {children, style: _style, onMouseOver, onMouseEnter, onMouseDown, onMouseLeave, onMouseMove, onMouseUp, onContextMenu, testID, flex, title, tooltip} = box2p const cn = box2ClassNames(box2p, 'clickable-box2') const s = Styles.collapseStyles([flex != null && flex !== 1 ? {flex} : undefined, _style]) as React.CSSProperties + const onKeyDown = + asButton && onClick + ? (e: React.KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + onClick(e) + } + } + : undefined return (
} @@ -330,13 +343,16 @@ export const ClickableBox = (p: ClickableBoxProps & {ref?: React.Ref {children} @@ -354,6 +370,7 @@ export const ClickableBox = (p: ClickableBoxProps & {ref?: React.Ref { onClick(e) } : undefined} pointerEvents={pointerEvents} + role={asButton ? 'button' : undefined} style={s} testID={box2p.testID} > diff --git a/shared/fs/browser/edit-state.tsx b/shared/fs/browser/edit-state.tsx index de350d41bc7d..ddde942d5529 100644 --- a/shared/fs/browser/edit-state.tsx +++ b/shared/fs/browser/edit-state.tsx @@ -321,8 +321,8 @@ export const FsBrowserEditProvider = ({children}: {children: React.ReactNode}) = }) return ( - + {children} - + ) } diff --git a/shared/fs/browser/sort-state.tsx b/shared/fs/browser/sort-state.tsx index 84edb03fb13a..549b7d9d2ca7 100644 --- a/shared/fs/browser/sort-state.tsx +++ b/shared/fs/browser/sort-state.tsx @@ -50,8 +50,8 @@ export const FsBrowserSortProvider = ({children}: {children: React.ReactNode}) = } return ( - + {children} - + ) } diff --git a/shared/fs/common/daemon.tsx b/shared/fs/common/daemon.tsx index 0f1d86b2ca27..df427e8e9031 100644 --- a/shared/fs/common/daemon.tsx +++ b/shared/fs/common/daemon.tsx @@ -184,9 +184,9 @@ export const FsDaemonProvider = ({children}: {children: React.ReactNode}) => { : Constants.unknownKbfsDaemonStatus return ( - - {children} - + + {children} + ) } diff --git a/shared/fs/common/error-state.tsx b/shared/fs/common/error-state.tsx index 6d06dd2cd987..bdfc4a920f0d 100644 --- a/shared/fs/common/error-state.tsx +++ b/shared/fs/common/error-state.tsx @@ -132,7 +132,7 @@ export const FsErrorProvider = ({children}: {children: React.ReactNode}) => { }) return ( - { }} > {children} - + ) } @@ -160,7 +160,7 @@ export const FsErrorContextBridge = ({ }: { children: React.ReactNode value: FsErrorContextType | null -}) => {children} +}) => {children} export const useFsErrors = () => { const routeErrors = React.useContext(FsErrorContext) diff --git a/shared/fs/common/hooks.tsx b/shared/fs/common/hooks.tsx index 5b4e61244bf4..51bc1e2d546c 100644 --- a/shared/fs/common/hooks.tsx +++ b/shared/fs/common/hooks.tsx @@ -107,7 +107,7 @@ export const FsDataContextBridge = ({ }: { children: React.ReactNode value: FsDataContextType | null -}) => {children} +}) => {children} type DownloadStartType = 'download' | 'share' | 'saveMedia' @@ -619,7 +619,7 @@ const FsDataProviderForUsername = ({ } return ( - {children} - + ) } diff --git a/shared/fs/common/sfmi.tsx b/shared/fs/common/sfmi.tsx index 063d2ce9c0de..113596f37603 100644 --- a/shared/fs/common/sfmi.tsx +++ b/shared/fs/common/sfmi.tsx @@ -342,7 +342,7 @@ export const SystemFileManagerIntegrationProvider = ({ ) return ( - {children} - + ) } diff --git a/shared/fs/common/status.tsx b/shared/fs/common/status.tsx index be63ea4ea685..8fde61933861 100644 --- a/shared/fs/common/status.tsx +++ b/shared/fs/common/status.tsx @@ -386,11 +386,11 @@ const FsStatusDataProvider = ({children}: {children: React.ReactNode}) => { connected && fsStatusState.generation === currentGeneration ? fsStatusState : emptyFsStatusState return ( - - + + {children} - - + + ) } diff --git a/shared/styles/theme.tsx b/shared/styles/theme.tsx index 0b5ebbef5536..fe5814662b09 100644 --- a/shared/styles/theme.tsx +++ b/shared/styles/theme.tsx @@ -45,7 +45,7 @@ export const ThemeProvider = (p: {children: React.ReactNode}) => { // The selector is constant-false off Android, so the store never notifies there. const isDarkMode = useDarkModeState(s => isAndroid && s.isDarkMode()) return ( - {p.children} + {p.children} ) } diff --git a/shared/teams/common/activity.tsx b/shared/teams/common/activity.tsx index 5473a08eb6f8..527034ba445e 100644 --- a/shared/teams/common/activity.tsx +++ b/shared/teams/common/activity.tsx @@ -104,7 +104,7 @@ export const ActivityLevelsProvider = (props: React.PropsWithChildren) => { ) ) const value = useActivityLevelsRaw(cache) - return {children} + return {children} } const Activity = (p: Props) => { diff --git a/shared/teams/common/selection-state.tsx b/shared/teams/common/selection-state.tsx index 161ce65d14ca..8090c017d31a 100644 --- a/shared/teams/common/selection-state.tsx +++ b/shared/teams/common/selection-state.tsx @@ -94,7 +94,7 @@ export const TeamSelectionProvider = (props: TeamSelectionProviderProps) => { setMemberSelected: members.set, } - return {props.children} + return {props.children} } export const useTeamSelectionState = () => { @@ -114,7 +114,7 @@ export const ChannelSelectionProvider = (props: ChannelSelectionProviderProps) = setMemberSelected: members.set, } - return {props.children} + return {props.children} } export const useChannelSelectionState = () => { diff --git a/shared/teams/common/use-loaded-team-channels.tsx b/shared/teams/common/use-loaded-team-channels.tsx index 8755f7c95226..d9e1d39d149b 100644 --- a/shared/teams/common/use-loaded-team-channels.tsx +++ b/shared/teams/common/use-loaded-team-channels.tsx @@ -254,7 +254,7 @@ export const LoadedTeamChannelsProvider = ( () => ({...loadedTeamChannels, teamID}), [loadedTeamChannels, teamID] ) - return {children} + return {children} } export const useLoadedTeamChannels = ( diff --git a/shared/teams/team/use-loaded-team.tsx b/shared/teams/team/use-loaded-team.tsx index e0774bbd51b8..c15ba232392d 100644 --- a/shared/teams/team/use-loaded-team.tsx +++ b/shared/teams/team/use-loaded-team.tsx @@ -206,7 +206,7 @@ export const LoadedTeamProvider = (props: React.PropsWithChildren<{teamID: T.Tea const {children, teamID} = props const loadedTeam = useLoadedTeamRaw(teamID) const value = React.useMemo(() => ({...loadedTeam, teamID}), [loadedTeam, teamID]) - return {children} + return {children} } export const useLoadedTeam = (teamID: T.Teams.TeamID, enabled = true): LoadedTeam => { diff --git a/shared/teams/use-teams-list.tsx b/shared/teams/use-teams-list.tsx index ee25ff4f86e0..8188cf30aaf0 100644 --- a/shared/teams/use-teams-list.tsx +++ b/shared/teams/use-teams-list.tsx @@ -213,9 +213,9 @@ export const LoadedTeamsListProvider = (props: React.PropsWithChildren) => { const teamsList = useTeamsListRaw() const teamsRoleMap = useTeamsRoleMapRaw() return ( - - {props.children} - + + {props.children} + ) } diff --git a/shared/tsconfig.native.json b/shared/tsconfig.native.json index 399783d67966..4f6dba111dfd 100644 --- a/shared/tsconfig.native.json +++ b/shared/tsconfig.native.json @@ -18,6 +18,7 @@ "./common-adapters/icon.constants-gen.native.tsx", "./common-adapters/icon.constants-gen.shared.tsx", "./chat/conversation/normal/container.test.tsx", + "./chat/conversation/messages/separator.test.tsx", "./chat/conversation/messages/system-users-added-to-conv/container.test.tsx", "./chat/conversation/messages/text/coinflip/results.test.tsx", "./common-adapters/markdown/index.test.tsx", diff --git a/shared/util/use-rpc-load.test.tsx b/shared/util/use-rpc-load.test.tsx index 53f90dc8837c..db45b3da71af 100644 --- a/shared/util/use-rpc-load.test.tsx +++ b/shared/util/use-rpc-load.test.tsx @@ -33,7 +33,7 @@ const makeNav = () => { }) } const wrapper = ({children}: {children: React.ReactNode}) => ( - {children} + {children} ) return {emit, wrapper} }