@@ -1876,6 +1876,192 @@ describe('ChannelList', () => {
18761876 expect ( results ) . toHaveNoViolations ( ) ;
18771877 } ) ;
18781878 } ) ;
1879+
1880+ describe ( 'predefined_filter response metadata' , ( ) => {
1881+ // Resolved `predefined_filter` from queryChannels response overrides the
1882+ // caller-supplied `filters`/`sort` props for local WS-driven channel list
1883+ // mutation decisions. See stream-chat PR #1747 for the underlying SDK
1884+ // change.
1885+
1886+ const mockQueryChannelsResponseWithPredefinedFilter = ( {
1887+ channels,
1888+ filter,
1889+ sort,
1890+ } : {
1891+ channels : ChannelAPIResponse [ ] ;
1892+ filter : Record < string , unknown > ;
1893+ sort ?: { direction ?: 1 | - 1 ; field : string } [ ] ;
1894+ } ) => {
1895+ ( vi . spyOn ( chatClient . axiosInstance , 'post' ) as unknown as Mock ) . mockResolvedValue (
1896+ {
1897+ data : {
1898+ channels,
1899+ duration : '0.01ms' ,
1900+ predefined_filter : {
1901+ filter,
1902+ name : 'messaging_channels' ,
1903+ sort,
1904+ } ,
1905+ } ,
1906+ status : 200 ,
1907+ } ,
1908+ ) ;
1909+ } ;
1910+
1911+ const channelListProps = {
1912+ filters : { } ,
1913+ options : {
1914+ limit : 25 ,
1915+ message_limit : 25 ,
1916+ predefined_filter : 'messaging_channels' ,
1917+ } ,
1918+ } ;
1919+
1920+ it ( 'does not promote an archived channel when resolved filter excludes archived' , async ( ) => {
1921+ mockQueryChannelsResponseWithPredefinedFilter ( {
1922+ channels : [ testChannel1 , testChannel2 , testChannel3 ] ,
1923+ filter : { archived : false } ,
1924+ } ) ;
1925+
1926+ const { getAllByRole, getByRole } = await render (
1927+ < Chat client = { chatClient } >
1928+ < WithComponents
1929+ overrides = { {
1930+ ChannelListItemUI : ChannelPreviewComponent ,
1931+ ChannelListUI : ChannelListComponent ,
1932+ } }
1933+ >
1934+ < ChannelList { ...channelListProps } />
1935+ </ WithComponents >
1936+ </ Chat > ,
1937+ ) ;
1938+
1939+ await waitFor ( ( ) => {
1940+ expect ( getByRole ( 'list' ) ) . toBeInTheDocument ( ) ;
1941+ } ) ;
1942+
1943+ // Mark target channel as archived after it has been loaded into the
1944+ // list so the response is still "non-archived" but local state for the
1945+ // channel is archived.
1946+ const targetChannel = chatClient . activeChannels [ testChannel3 . channel . cid ] ;
1947+ targetChannel . state . membership = {
1948+ archived_at : '2024-01-15T10:30:00Z' ,
1949+ } ;
1950+
1951+ const itemsBefore = getAllByRole ( 'listitem' ) . map ( ( el ) =>
1952+ el . getAttribute ( 'data-testid' ) ,
1953+ ) ;
1954+
1955+ await act ( ( ) => {
1956+ dispatchMessageNewEvent (
1957+ chatClient ,
1958+ generateMessage ( { user : generateUser ( ) } ) ,
1959+ testChannel3 . channel ,
1960+ ) ;
1961+ } ) ;
1962+
1963+ const itemsAfter = getAllByRole ( 'listitem' ) . map ( ( el ) =>
1964+ el . getAttribute ( 'data-testid' ) ,
1965+ ) ;
1966+
1967+ expect ( itemsAfter ) . toStrictEqual ( itemsBefore ) ;
1968+ } ) ;
1969+
1970+ it ( 'does not move a pinned channel when resolved sort considers pinned_at' , async ( ) => {
1971+ mockQueryChannelsResponseWithPredefinedFilter ( {
1972+ channels : [ testChannel1 , testChannel2 , testChannel3 ] ,
1973+ filter : { } ,
1974+ sort : [ { direction : - 1 , field : 'pinned_at' } ] ,
1975+ } ) ;
1976+
1977+ const { getAllByRole, getByRole } = await render (
1978+ < Chat client = { chatClient } >
1979+ < WithComponents
1980+ overrides = { {
1981+ ChannelListItemUI : ChannelPreviewComponent ,
1982+ ChannelListUI : ChannelListComponent ,
1983+ } }
1984+ >
1985+ < ChannelList { ...channelListProps } />
1986+ </ WithComponents >
1987+ </ Chat > ,
1988+ ) ;
1989+
1990+ await waitFor ( ( ) => {
1991+ expect ( getByRole ( 'list' ) ) . toBeInTheDocument ( ) ;
1992+ } ) ;
1993+
1994+ // Mark target channel as pinned in local state after it has been
1995+ // loaded.
1996+ const targetChannel = chatClient . activeChannels [ testChannel3 . channel . cid ] ;
1997+ targetChannel . state . membership = {
1998+ pinned_at : '2024-01-15T10:30:00Z' ,
1999+ } ;
2000+
2001+ const itemsBefore = getAllByRole ( 'listitem' ) . map ( ( el ) =>
2002+ el . getAttribute ( 'data-testid' ) ,
2003+ ) ;
2004+
2005+ await act ( ( ) => {
2006+ dispatchMessageNewEvent (
2007+ chatClient ,
2008+ generateMessage ( { user : generateUser ( ) } ) ,
2009+ testChannel3 . channel ,
2010+ ) ;
2011+ } ) ;
2012+
2013+ const itemsAfter = getAllByRole ( 'listitem' ) . map ( ( el ) =>
2014+ el . getAttribute ( 'data-testid' ) ,
2015+ ) ;
2016+
2017+ expect ( itemsAfter ) . toStrictEqual ( itemsBefore ) ;
2018+ } ) ;
2019+
2020+ it ( 'falls back to caller filters/sort when response has no predefined_filter' , async ( ) => {
2021+ useMockedApis ( chatClient , [
2022+ queryChannelsApi ( [ testChannel1 , testChannel2 , testChannel3 ] ) ,
2023+ ] ) ;
2024+
2025+ const { getAllByRole, getByRole, getByText } = await render (
2026+ < Chat client = { chatClient } >
2027+ < WithComponents
2028+ overrides = { {
2029+ ChannelListItemUI : ChannelPreviewComponent ,
2030+ ChannelListUI : ChannelListComponent ,
2031+ } }
2032+ >
2033+ < ChannelList filters = { { } } options = { { limit : 25 , message_limit : 25 } } />
2034+ </ WithComponents >
2035+ </ Chat > ,
2036+ ) ;
2037+
2038+ await waitFor ( ( ) => {
2039+ expect ( getByRole ( 'list' ) ) . toBeInTheDocument ( ) ;
2040+ } ) ;
2041+
2042+ // Even with archived local membership, the absence of an effective
2043+ // `archived` filter means the channel should still be promoted.
2044+ const targetChannel = chatClient . activeChannels [ testChannel3 . channel . cid ] ;
2045+ targetChannel . state . membership = {
2046+ archived_at : '2024-01-15T10:30:00Z' ,
2047+ } ;
2048+
2049+ const newMessage = generateMessage ( { user : generateUser ( ) } ) ;
2050+ await act ( ( ) => {
2051+ dispatchMessageNewEvent ( chatClient , newMessage , testChannel3 . channel ) ;
2052+ } ) ;
2053+
2054+ await waitFor ( ( ) => {
2055+ expect ( getByText ( newMessage . text ) ) . toBeInTheDocument ( ) ;
2056+ } ) ;
2057+
2058+ const items = getAllByRole ( 'listitem' ) ;
2059+ const channelPreview = getByText ( newMessage . text ) . closest (
2060+ ROLE_LIST_ITEM_SELECTOR ,
2061+ ) ;
2062+ expect ( channelPreview ?. isEqualNode ( items [ 0 ] ) ) . toBe ( true ) ;
2063+ } ) ;
2064+ } ) ;
18792065 } ) ;
18802066
18812067 describe ( 'on connection recovery' , ( ) => {
0 commit comments