Merge pull request #23940 from overleaf/td-react-18

Upgrade to React 18

GitOrigin-RevId: 9b81936e6eea2bccd97fe5c2c5841f0b946371b8
This commit is contained in:
Tim Down
2025-05-02 08:05:29 +00:00
committed by Copybot
parent 4464320757
commit 7abafb01ea
213 changed files with 2469 additions and 2105 deletions
@@ -1,7 +1,7 @@
// Disable prop type checks for test harnesses
/* eslint-disable react/prop-types */
import { renderHook, act } from '@testing-library/react-hooks/dom'
import { renderHook, act, waitFor } from '@testing-library/react'
import { expect } from 'chai'
import sinon from 'sinon'
import fetchMock from 'fetch-mock'
@@ -30,6 +30,7 @@ describe('ChatContext', function () {
window.metaAttributesCache.set('ol-user', user)
window.metaAttributesCache.set('ol-chatEnabled', true)
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
this.stub = sinon.stub(chatClientIdGenerator, 'generate').returns(uuidValue)
})
@@ -43,8 +44,9 @@ describe('ChatContext', function () {
describe('socket connection', function () {
beforeEach(function () {
// Mock GET messages to return no messages
// FIXME?
// fetchMock.get('express:/project/:projectId/messages', [])
fetchMock.get('express:/project/:projectId/messages', [], {
name: 'fetchMessages',
})
// Mock POST new message to return 200
fetchMock.post('express:/project/:projectId/messages', 200)
@@ -72,13 +74,15 @@ describe('ChatContext', function () {
it('adds received messages to the list', async function () {
// Mock socket: we only need to emit events, not mock actual connections
const socket = new SocketIOMock()
const { result, waitForNextUpdate } = renderChatContextHook({
const { result } = renderChatContextHook({
socket,
})
// Wait until initial messages have loaded
result.current.loadInitialMessages()
await waitForNextUpdate()
await waitFor(
() => expect(result.current.initialMessagesLoaded).to.be.true
)
// No messages shown at first
expect(result.current.messages).to.deep.equal([])
@@ -95,30 +99,34 @@ describe('ChatContext', function () {
},
})
const message = result.current.messages[0]
expect(message.id).to.equal('msg_1')
expect(message.contents).to.deep.equal(['new message'])
await waitFor(() => {
const message = result.current.messages[0]
expect(message.id).to.equal('msg_1')
expect(message.contents).to.deep.equal(['new message'])
})
})
it('deduplicate messages from preloading', async function () {
// Mock socket: we only need to emit events, not mock actual connections
const socket = new SocketIOMock()
const { result, waitForNextUpdate } = renderChatContextHook({
const { result } = renderChatContextHook({
socket,
})
fetchMock.get('express:/project/:projectId/messages', [
{
id: 'msg_1',
content: 'new message',
timestamp: Date.now(),
user: {
id: 'another_fake_user',
first_name: 'another_fake_user_first_name',
email: 'another_fake@example.com',
fetchMock.modifyRoute('fetchMessages', {
response: () => [
{
id: 'msg_1',
content: 'new message',
timestamp: Date.now(),
user: {
id: 'another_fake_user',
first_name: 'another_fake_user_first_name',
email: 'another_fake@example.com',
},
},
},
])
],
})
// Mock message being received from another user
socket.emitToClient('new-chat-message', {
@@ -133,11 +141,13 @@ describe('ChatContext', function () {
})
// Check if received the message ID
expect(result.current.messages).to.have.length(1)
await waitFor(() => expect(result.current.messages).to.have.length(1))
// Wait until initial messages have loaded
result.current.loadInitialMessages()
await waitForNextUpdate()
await waitFor(
() => expect(result.current.initialMessagesLoaded).to.be.true
)
// Check if there are no message duplication
expect(result.current.messages).to.have.length(1)
@@ -150,29 +160,30 @@ describe('ChatContext', function () {
it('deduplicate messages from websocket', async function () {
// Mock socket: we only need to emit events, not mock actual connections
const socket = new SocketIOMock()
const { result, waitForNextUpdate } = renderChatContextHook({
const { result } = renderChatContextHook({
socket,
})
fetchMock.get('express:/project/:projectId/messages', [
{
id: 'msg_1',
content: 'new message',
timestamp: Date.now(),
user: {
id: 'another_fake_user',
first_name: 'another_fake_user_first_name',
email: 'another_fake@example.com',
fetchMock.modifyRoute('fetchMessages', {
response: [
{
id: 'msg_1',
content: 'new message',
timestamp: Date.now(),
user: {
id: 'another_fake_user',
first_name: 'another_fake_user_first_name',
email: 'another_fake@example.com',
},
},
},
])
],
})
// Wait until initial messages have loaded
result.current.loadInitialMessages()
await waitForNextUpdate()
// Check if received the message ID
expect(result.current.messages).to.have.length(1)
await waitFor(() => expect(result.current.messages).to.have.length(1))
// Mock message being received from another user
socket.emitToClient('new-chat-message', {
@@ -196,13 +207,15 @@ describe('ChatContext', function () {
it("doesn't add received messages from the current user if a message was just sent", async function () {
const socket = new SocketIOMock()
const { result, waitForNextUpdate } = renderChatContextHook({
const { result } = renderChatContextHook({
socket,
})
// Wait until initial messages have loaded
result.current.loadInitialMessages()
await waitForNextUpdate()
await waitFor(
() => expect(result.current.initialMessagesLoaded).to.be.true
)
// Send a message from the current user
const sentMsg = 'sent message'
@@ -219,7 +232,7 @@ describe('ChatContext', function () {
})
})
expect(result.current.messages).to.have.length(1)
await waitFor(() => expect(result.current.messages).to.have.length(1))
const [message] = result.current.messages
@@ -228,17 +241,21 @@ describe('ChatContext', function () {
it('adds the new message from the current user if another message was received after sending', async function () {
const socket = new SocketIOMock()
const { result, waitForNextUpdate } = renderChatContextHook({
const { result } = renderChatContextHook({
socket,
})
// Wait until initial messages have loaded
result.current.loadInitialMessages()
await waitForNextUpdate()
await waitFor(
() => expect(result.current.initialMessagesLoaded).to.be.true
)
// Send a message from the current user
const sentMsg = 'sent message from current user'
result.current.sendMessage(sentMsg)
act(() => {
result.current.sendMessage(sentMsg)
})
const [sentMessageFromCurrentUser] = result.current.messages
expect(sentMessageFromCurrentUser.contents).to.deep.equal([sentMsg])
@@ -295,24 +312,29 @@ describe('ChatContext', function () {
})
it('adds messages to the list', async function () {
const { result, waitForNextUpdate } = renderChatContextHook({})
const { result } = renderChatContextHook({})
result.current.loadInitialMessages()
await waitForNextUpdate()
expect(result.current.messages[0].contents).to.deep.equal(['a message'])
await waitFor(() =>
expect(result.current.messages[0].contents).to.deep.equal(['a message'])
)
})
it("won't load messages a second time", async function () {
const { result, waitForNextUpdate } = renderChatContextHook({})
const { result } = renderChatContextHook({})
result.current.loadInitialMessages()
await waitForNextUpdate()
expect(result.current.initialMessagesLoaded).to.equal(true)
await waitFor(() =>
expect(result.current.initialMessagesLoaded).to.equal(true)
)
// Calling a second time won't do anything
result.current.loadInitialMessages()
await waitFor(
() => expect(result.current.initialMessagesLoaded).to.be.true
)
expect(
fetchMock.callHistory.calls('express:/project/:projectId/messages')
).to.have.lengthOf(1)
@@ -321,12 +343,10 @@ describe('ChatContext', function () {
it('provides an error on failure', async function () {
fetchMock.removeRoutes().clearHistory()
fetchMock.get('express:/project/:projectId/messages', 500)
const { result, waitForNextUpdate } = renderChatContextHook({})
const { result } = renderChatContextHook({})
result.current.loadInitialMessages()
await waitForNextUpdate()
expect(result.current.error).to.exist
await waitFor(() => expect(result.current.error).to.exist)
expect(result.current.status).to.equal('error')
})
})
@@ -343,14 +363,14 @@ describe('ChatContext', function () {
},
])
const { result, waitForNextUpdate } = renderChatContextHook({})
const { result } = renderChatContextHook({})
result.current.loadMoreMessages()
await waitForNextUpdate()
expect(result.current.messages[0].contents).to.deep.equal([
'first message',
])
await waitFor(() =>
expect(result.current.messages[0].contents).to.deep.equal([
'first message',
])
)
// The before query param is not set
expect(getLastFetchMockQueryParam('before')).to.be.null
@@ -373,20 +393,23 @@ describe('ChatContext', function () {
},
])
const { result, waitForNextUpdate } = renderChatContextHook({})
const { result } = renderChatContextHook({})
result.current.loadMoreMessages()
await waitForNextUpdate()
await waitFor(() =>
expect(result.current.messages[0].contents).to.have.length(50)
)
// Call a second time
result.current.loadMoreMessages()
await waitForNextUpdate()
// The second request is added to the list
// Since both messages from the same user, they are collapsed into the
// same "message"
expect(result.current.messages[0].contents).to.include(
'message from second page'
await waitFor(() =>
expect(result.current.messages[0].contents).to.include(
'message from second page'
)
)
// The before query param for the second request matches the timestamp
@@ -396,23 +419,25 @@ describe('ChatContext', function () {
})
it("won't load more messages if there are no more messages", async function () {
// Mock a GET request for 49 messages. This is less the the full page size
// (50 messages), meaning that there are no further messages to be loaded
// Mock a GET request for 49 messages. This is less than the full page
// size (50 messages), meaning that there are no further messages to be
// loaded
fetchMock.getOnce(
'express:/project/:projectId/messages',
createMessages(49, user)
)
const { result, waitForNextUpdate } = renderChatContextHook({})
const { result } = renderChatContextHook({})
result.current.loadMoreMessages()
await waitForNextUpdate()
expect(result.current.messages[0].contents).to.have.length(49)
await waitFor(() =>
expect(result.current.messages[0].contents).to.have.length(49)
)
result.current.loadMoreMessages()
expect(result.current.atEnd).to.be.true
await waitFor(() => expect(result.current.atEnd).to.be.true)
expect(
fetchMock.callHistory.calls('express:/project/:projectId/messages')
).to.have.lengthOf(1)
@@ -429,7 +454,7 @@ describe('ChatContext', function () {
)
const socket = new SocketIOMock()
const { result, waitForNextUpdate } = renderChatContextHook({
const { result } = renderChatContextHook({
socket,
})
@@ -458,28 +483,27 @@ describe('ChatContext', function () {
timestamp: Date.now(),
},
])
await waitForNextUpdate()
// Although the loaded message was resolved last, it appears first (since
// requested messages must have come first)
const messageContents = result.current.messages.map(
({ contents }) => contents[0]
)
expect(messageContents).to.deep.equal([
'loaded message',
'socket message',
])
await waitFor(() => {
// Although the loaded message was resolved last, it appears first (since
// requested messages must have come first)
const messageContents = result.current.messages.map(
({ contents }) => contents[0]
)
expect(messageContents).to.deep.equal([
'loaded message',
'socket message',
])
})
})
it('provides an error on failures', async function () {
fetchMock.removeRoutes().clearHistory()
fetchMock.get('express:/project/:projectId/messages', 500)
const { result, waitForNextUpdate } = renderChatContextHook({})
const { result } = renderChatContextHook({})
result.current.loadMoreMessages()
await waitForNextUpdate()
expect(result.current.error).to.exist
await waitFor(() => expect(result.current.error).to.exist)
expect(result.current.status).to.equal('error')
})
})
@@ -493,14 +517,16 @@ describe('ChatContext', function () {
.postOnce('express:/project/:projectId/messages', 200)
})
it('optimistically adds the message to the list', function () {
it('optimistically adds the message to the list', async function () {
const { result } = renderChatContextHook({})
result.current.sendMessage('sent message')
expect(result.current.messages[0].contents).to.deep.equal([
'sent message',
])
await waitFor(() =>
expect(result.current.messages[0].contents).to.deep.equal([
'sent message',
])
)
})
it('POSTs the message to the backend', function () {
@@ -534,12 +560,11 @@ describe('ChatContext', function () {
fetchMock
.get('express:/project/:projectId/messages', [])
.postOnce('express:/project/:projectId/messages', 500)
const { result, waitForNextUpdate } = renderChatContextHook({})
const { result } = renderChatContextHook({})
result.current.sendMessage('sent message')
await waitForNextUpdate()
await waitFor(() => expect(result.current.error).to.exist)
expect(result.current.error).to.exist
expect(result.current.status).to.equal('error')
})
})
@@ -555,11 +580,13 @@ describe('ChatContext', function () {
const { result } = renderChatContextHook({ socket })
// Receive a new message from the socket
socket.emitToClient('new-chat-message', {
id: 'msg_1',
content: 'new message',
timestamp: Date.now(),
user,
act(() => {
socket.emitToClient('new-chat-message', {
id: 'msg_1',
content: 'new message',
timestamp: Date.now(),
user,
})
})
expect(result.current.unreadMessageCount).to.equal(1)