mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 14:13:43 +01:00
Merge pull request #23940 from overleaf/td-react-18
Upgrade to React 18 GitOrigin-RevId: 9b81936e6eea2bccd97fe5c2c5841f0b946371b8
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { EditorProviders } from '../../helpers/editor-providers'
|
||||
import PdfJsViewer from '../../../../frontend/js/features/pdf-preview/components/pdf-js-viewer'
|
||||
import { mockScope } from './scope'
|
||||
import { getContainerEl } from 'cypress/react'
|
||||
import { getContainerEl } from 'cypress/react18'
|
||||
import { unmountComponentAtNode } from 'react-dom'
|
||||
import { PdfPreviewProvider } from '../../../../frontend/js/features/pdf-preview/components/pdf-preview-provider'
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('<PdfLogsEntries/>', function () {
|
||||
entity: { _id: '123', name: '123 Doc' },
|
||||
}
|
||||
|
||||
const FileTreePathProvider: FC = ({ children }) => (
|
||||
const FileTreePathProvider: FC<React.PropsWithChildren> = ({ children }) => (
|
||||
<FileTreePathContext.Provider
|
||||
value={{
|
||||
dirname: cy.stub(),
|
||||
@@ -34,7 +34,7 @@ describe('<PdfLogsEntries/>', function () {
|
||||
</FileTreePathContext.Provider>
|
||||
)
|
||||
|
||||
const EditorManagerProvider: FC = ({ children }) => {
|
||||
const EditorManagerProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||
const value = {
|
||||
openDocWithId: cy.spy().as('openDocWithId'),
|
||||
// @ts-ignore
|
||||
|
||||
@@ -23,6 +23,7 @@ describe('<ChatPane />', function () {
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-user', user)
|
||||
window.metaAttributesCache.set('ol-chatEnabled', true)
|
||||
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
@@ -8,6 +8,7 @@ import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
describe('<EditorCloneProjectModalWrapper />', function () {
|
||||
beforeEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
|
||||
})
|
||||
|
||||
after(function () {
|
||||
|
||||
+6
-10
@@ -4,22 +4,17 @@ import sinon from 'sinon'
|
||||
import { expect } from 'chai'
|
||||
import ActionsCopyProject from '../../../../../frontend/js/features/editor-left-menu/components/actions-copy-project'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
import * as useLocationModule from '../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
|
||||
describe('<ActionsCopyProject />', function () {
|
||||
let assignStub
|
||||
|
||||
beforeEach(function () {
|
||||
assignStub = sinon.stub()
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: assignStub,
|
||||
replace: sinon.stub(),
|
||||
reload: sinon.stub(),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
@@ -53,6 +48,7 @@ describe('<ActionsCopyProject />', function () {
|
||||
expect(button.textContent).to.equal('Copying…')
|
||||
})
|
||||
|
||||
const assignStub = this.locationWrapperStub.assign
|
||||
await waitFor(() => {
|
||||
expect(assignStub).to.have.been.calledOnceWith('/project/new-project')
|
||||
})
|
||||
|
||||
+50
-40
@@ -1,7 +1,7 @@
|
||||
import sinon from 'sinon'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import { expect } from 'chai'
|
||||
import { screen } from '@testing-library/react'
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import LayoutDropdownButton from '../../../../../frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
import * as eventTracking from '@/infrastructure/event-tracking'
|
||||
@@ -31,13 +31,15 @@ describe('<LayoutDropdownButton />', function () {
|
||||
|
||||
screen.getByRole('button', { name: 'Layout' }).click()
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor & PDF',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor & PDF',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
)
|
||||
|
||||
expect(
|
||||
screen
|
||||
@@ -64,7 +66,7 @@ describe('<LayoutDropdownButton />', function () {
|
||||
).to.equal('false')
|
||||
})
|
||||
|
||||
it('should not select any option in history view', function () {
|
||||
it('should not select any option in history view', async function () {
|
||||
// Selected is aria-label, visually we show a checkmark
|
||||
renderWithEditorContext(<LayoutDropdownButton />, {
|
||||
ui: { ...defaultUi, view: 'history' },
|
||||
@@ -72,13 +74,15 @@ describe('<LayoutDropdownButton />', function () {
|
||||
|
||||
screen.getByRole('button', { name: 'Layout' }).click()
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor & PDF',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor & PDF',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
)
|
||||
|
||||
expect(
|
||||
screen
|
||||
@@ -105,7 +109,7 @@ describe('<LayoutDropdownButton />', function () {
|
||||
).to.equal('false')
|
||||
})
|
||||
|
||||
it('should treat file and editor views the same way', function () {
|
||||
it('should treat file and editor views the same way', async function () {
|
||||
// Selected is aria-label, visually we show a checkmark
|
||||
renderWithEditorContext(<LayoutDropdownButton />, {
|
||||
ui: {
|
||||
@@ -116,13 +120,15 @@ describe('<LayoutDropdownButton />', function () {
|
||||
|
||||
screen.getByRole('button', { name: 'Layout' }).click()
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor & PDF',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor & PDF',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
)
|
||||
|
||||
expect(
|
||||
screen
|
||||
@@ -149,9 +155,9 @@ describe('<LayoutDropdownButton />', function () {
|
||||
).to.equal('false')
|
||||
})
|
||||
|
||||
describe('on detach', function () {
|
||||
describe('on detach', async function () {
|
||||
let originalBroadcastChannel
|
||||
beforeEach(function () {
|
||||
beforeEach(async function () {
|
||||
window.BroadcastChannel = originalBroadcastChannel || true // ensure that window.BroadcastChannel is truthy
|
||||
|
||||
renderWithEditorContext(<LayoutDropdownButton />, {
|
||||
@@ -160,19 +166,21 @@ describe('<LayoutDropdownButton />', function () {
|
||||
|
||||
screen.getByRole('button', { name: 'Layout' }).click()
|
||||
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'PDF in separate tab',
|
||||
})
|
||||
.click()
|
||||
await waitFor(() =>
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'PDF in separate tab',
|
||||
})
|
||||
.click()
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
window.BroadcastChannel = originalBroadcastChannel
|
||||
})
|
||||
|
||||
it('should show processing', function () {
|
||||
screen.getByText('Layout processing')
|
||||
it('should show processing', async function () {
|
||||
await screen.findByText('Layout processing')
|
||||
})
|
||||
|
||||
it('should record event', function () {
|
||||
@@ -180,8 +188,8 @@ describe('<LayoutDropdownButton />', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('on layout change / reattach', function () {
|
||||
beforeEach(function () {
|
||||
describe('on layout change / reattach', async function () {
|
||||
beforeEach(async function () {
|
||||
window.metaAttributesCache.set('ol-detachRole', 'detacher')
|
||||
renderWithEditorContext(<LayoutDropdownButton />, {
|
||||
ui: { ...defaultUi, view: 'editor' },
|
||||
@@ -189,11 +197,13 @@ describe('<LayoutDropdownButton />', function () {
|
||||
|
||||
screen.getByRole('button', { name: 'Layout' }).click()
|
||||
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor only (hide PDF)',
|
||||
})
|
||||
.click()
|
||||
await waitFor(() =>
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor only (hide PDF)',
|
||||
})
|
||||
.click()
|
||||
)
|
||||
})
|
||||
|
||||
it('should not show processing', function () {
|
||||
|
||||
+1
@@ -28,6 +28,7 @@ describe('<ToolbarHeader />', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-chatEnabled', true)
|
||||
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
|
||||
})
|
||||
|
||||
describe('cobranding logo', function () {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import FileTreeFolder from '../../../../../frontend/js/features/file-tree/components/file-tree-folder'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import { FileTreeProvider } from '../helpers/file-tree-provider'
|
||||
import { getContainerEl } from 'cypress/react'
|
||||
import { getContainerEl } from 'cypress/react18'
|
||||
import ReactDom from 'react-dom'
|
||||
|
||||
describe('<FileTreeFolder/>', function () {
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ComponentProps, FC, useRef, useState } from 'react'
|
||||
import FileTreeContext from '@/features/file-tree/components/file-tree-context'
|
||||
|
||||
export const FileTreeProvider: FC<{
|
||||
refProviders?: Record<string, boolean>
|
||||
}> = ({ children, refProviders = {} }) => {
|
||||
export const FileTreeProvider: FC<
|
||||
React.PropsWithChildren<{
|
||||
refProviders?: Record<string, boolean>
|
||||
}>
|
||||
> = ({ children, refProviders = {} }) => {
|
||||
const [fileTreeContainer, setFileTreeContainer] =
|
||||
useState<HTMLDivElement | null>(null)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ describe('<FileViewText/>', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
|
||||
})
|
||||
|
||||
it('renders a text view', async function () {
|
||||
|
||||
@@ -33,6 +33,7 @@ describe('<FileView/>', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
|
||||
})
|
||||
|
||||
describe('for a text file', function () {
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@ const createInitialValue = () =>
|
||||
setProjectSearchIsOpen: cy.stub(),
|
||||
}) satisfies LayoutContextValue
|
||||
|
||||
const LayoutProvider: FC = ({ children }) => {
|
||||
const LayoutProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||
const [value] = useState(createInitialValue)
|
||||
|
||||
return (
|
||||
|
||||
+4
-6
@@ -1,5 +1,5 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { ReactElement } from 'react'
|
||||
import { FC, PropsWithChildren, ReactElement } from 'react'
|
||||
import sinon from 'sinon'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import UnlinkUserModal from '@/features/group-management/components/members-table/unlink-user-modal'
|
||||
@@ -7,11 +7,9 @@ import { GroupMembersProvider } from '@/features/group-management/context/group-
|
||||
import { expect } from 'chai'
|
||||
|
||||
export function renderWithContext(component: ReactElement, props = {}) {
|
||||
const GroupMembersProviderWrapper = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactElement
|
||||
}) => <GroupMembersProvider {...props}>{children}</GroupMembersProvider>
|
||||
const GroupMembersProviderWrapper: FC<PropsWithChildren> = ({ children }) => (
|
||||
<GroupMembersProvider {...props}>{children}</GroupMembersProvider>
|
||||
)
|
||||
|
||||
return render(component, { wrapper: GroupMembersProviderWrapper })
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ const highlights: Highlight[] = [
|
||||
},
|
||||
]
|
||||
|
||||
const Container: FC = ({ children }) => (
|
||||
const Container: FC<React.PropsWithChildren> = ({ children }) => (
|
||||
<div style={{ width: 600, height: 400 }}>{children}</div>
|
||||
)
|
||||
|
||||
|
||||
+6
-12
@@ -3,23 +3,16 @@ import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import sinon from 'sinon'
|
||||
import ModalContentNewProjectForm from '../../../../../../frontend/js/features/project-list/components/new-project-button/modal-content-new-project-form'
|
||||
import * as useLocationModule from '../../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
|
||||
describe('<ModalContentNewProjectForm />', function () {
|
||||
let assignStub: sinon.SinonStub
|
||||
|
||||
beforeEach(function () {
|
||||
assignStub = sinon.stub()
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: assignStub,
|
||||
replace: sinon.stub(),
|
||||
reload: sinon.stub(),
|
||||
setHash: sinon.stub(),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
@@ -51,10 +44,11 @@ describe('<ModalContentNewProjectForm />', function () {
|
||||
|
||||
expect(newProjectMock.callHistory.called()).to.be.true
|
||||
|
||||
const assignStub = this.locationWrapperStub.assign
|
||||
await waitFor(() => {
|
||||
sinon.assert.calledOnce(assignStub)
|
||||
sinon.assert.calledWith(assignStub, `/project/${projectId}`)
|
||||
})
|
||||
sinon.assert.calledWith(assignStub, `/project/${projectId}`)
|
||||
})
|
||||
|
||||
it('shows error when project name contains "/"', async function () {
|
||||
|
||||
+23
-28
@@ -41,7 +41,7 @@ import { Project } from '../../../../../types/project/dashboard/api'
|
||||
import GroupsAndEnterpriseBanner from '../../../../../frontend/js/features/project-list/components/notifications/groups-and-enterprise-banner'
|
||||
import GroupSsoSetupSuccess from '../../../../../frontend/js/features/project-list/components/notifications/groups/group-sso-setup-success'
|
||||
import localStorage from '@/infrastructure/local-storage'
|
||||
import * as useLocationModule from '../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
import {
|
||||
commonsSubscription,
|
||||
freeSubscription,
|
||||
@@ -50,7 +50,9 @@ import {
|
||||
} from '../fixtures/user-subscriptions'
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
const renderWithinProjectListProvider = (Component: React.ComponentType) => {
|
||||
const renderWithinProjectListProvider = (
|
||||
Component: React.ComponentType<React.PropsWithChildren>
|
||||
) => {
|
||||
render(<Component />, {
|
||||
wrapper: ({ children }) => (
|
||||
<ProjectListProvider>
|
||||
@@ -655,12 +657,10 @@ describe('<UserNotifications />', function () {
|
||||
window.metaAttributesCache.set('ol-user', {
|
||||
signUpDate: new Date('2024-01-01').toISOString(),
|
||||
})
|
||||
this.clock = sinon.useFakeTimers(new Date('2025-07-01').getTime())
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
this.clock.restore()
|
||||
})
|
||||
|
||||
function testUnconfirmedNotification(
|
||||
@@ -675,7 +675,9 @@ describe('<UserNotifications />', function () {
|
||||
fetchMock.post('/user/emails/resend_confirmation', 200)
|
||||
|
||||
const email = userEmails[0].email
|
||||
const notificationBody = screen.getByTestId('pro-notification-body')
|
||||
const notificationBody = await screen.findByTestId(
|
||||
'pro-notification-body'
|
||||
)
|
||||
|
||||
if (isPrimary) {
|
||||
expect(notificationBody.textContent).to.contain(
|
||||
@@ -691,7 +693,7 @@ describe('<UserNotifications />', function () {
|
||||
fireEvent.click(resendButton)
|
||||
|
||||
await waitForElementToBeRemoved(() =>
|
||||
screen.getByRole('button', { name: /resend/i })
|
||||
screen.queryByRole('button', { name: /resend/i })
|
||||
)
|
||||
|
||||
expect(fetchMock.callHistory.called()).to.be.true
|
||||
@@ -717,7 +719,7 @@ describe('<UserNotifications />', function () {
|
||||
fetchMock.post('/user/emails/resend_confirmation', 200)
|
||||
|
||||
const email = untrustedUserData.email
|
||||
const notificationBody = screen.getByTestId(
|
||||
const notificationBody = await screen.findByTestId(
|
||||
'not-trusted-notification-body'
|
||||
)
|
||||
expect(notificationBody.textContent).to.contain(
|
||||
@@ -742,7 +744,9 @@ describe('<UserNotifications />', function () {
|
||||
await fetchMock.callHistory.flush(true)
|
||||
fetchMock.post('/user/emails/resend_confirmation', 500)
|
||||
|
||||
const resendButtons = screen.getAllByRole('button', { name: /resend/i })
|
||||
const resendButtons = await screen.findAllByRole('button', {
|
||||
name: /resend/i,
|
||||
})
|
||||
const resendButton = resendButtons[0]
|
||||
fireEvent.click(resendButton)
|
||||
const notificationBody = screen.getByTestId('pro-notification-body')
|
||||
@@ -767,7 +771,7 @@ describe('<UserNotifications />', function () {
|
||||
renderWithinProjectListProvider(ConfirmEmail)
|
||||
await fetchMock.callHistory.flush(true)
|
||||
|
||||
const alert = screen.getByRole('alert')
|
||||
const alert = await screen.findByRole('alert')
|
||||
const email = unconfirmedCommonsUserData.email
|
||||
const notificationBody = within(alert).getByTestId('notification-body')
|
||||
expect(notificationBody.textContent).to.contain(
|
||||
@@ -788,7 +792,7 @@ describe('<UserNotifications />', function () {
|
||||
renderWithinProjectListProvider(ConfirmEmail)
|
||||
await fetchMock.callHistory.flush(true)
|
||||
|
||||
const alert = screen.getByRole('alert')
|
||||
const alert = await screen.findByRole('alert')
|
||||
const email = unconfirmedCommonsUserData.email
|
||||
const notificationBody = within(alert).getByTestId(
|
||||
'pro-notification-body'
|
||||
@@ -808,22 +812,15 @@ describe('<UserNotifications />', function () {
|
||||
})
|
||||
|
||||
describe('<Affiliation/>', function () {
|
||||
let assignStub: sinon.SinonStub
|
||||
|
||||
beforeEach(function () {
|
||||
Object.assign(getMeta('ol-ExposedSettings'), exposedSettings)
|
||||
assignStub = sinon.stub()
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: assignStub,
|
||||
replace: sinon.stub(),
|
||||
reload: sinon.stub(),
|
||||
setHash: sinon.stub(),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
@@ -879,9 +876,9 @@ describe('<UserNotifications />', function () {
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: /confirm affiliation/i })
|
||||
)
|
||||
sinon.assert.calledOnce(assignStub)
|
||||
sinon.assert.calledOnce(this.locationWrapperStub.assign)
|
||||
sinon.assert.calledWithMatch(
|
||||
assignStub,
|
||||
this.locationWrapperStub.assign,
|
||||
`${exposedSettings.samlInitPath}?university_id=${professionalUserData.affiliation.institution.id}&reconfirm=/project`
|
||||
)
|
||||
})
|
||||
@@ -944,8 +941,7 @@ describe('<UserNotifications />', function () {
|
||||
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
|
||||
await fetchMock.callHistory.flush(true)
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.not.be
|
||||
.null
|
||||
await screen.findByRole('button', { name: 'Contact Sales' })
|
||||
})
|
||||
|
||||
it('shows the banner for users that have dismissed the banner more than 30 days ago', async function () {
|
||||
@@ -960,8 +956,7 @@ describe('<UserNotifications />', function () {
|
||||
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
|
||||
await fetchMock.callHistory.flush(true)
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.not.be
|
||||
.null
|
||||
await screen.findByRole('button', { name: 'Contact Sales' })
|
||||
})
|
||||
|
||||
it('does not show the banner for users that have dismissed the banner within the last 30 days', async function () {
|
||||
@@ -1014,7 +1009,7 @@ describe('<UserNotifications />', function () {
|
||||
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
|
||||
await fetchMock.callHistory.flush(true)
|
||||
|
||||
screen.getByText(
|
||||
await screen.findByText(
|
||||
'Overleaf On-Premises: Does your company want to keep its data within its firewall? Overleaf offers Server Pro, an on-premises solution for companies. Get in touch to learn more.'
|
||||
)
|
||||
const link = screen.getByRole('button', { name: 'Contact Sales' })
|
||||
@@ -1031,7 +1026,7 @@ describe('<UserNotifications />', function () {
|
||||
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
|
||||
await fetchMock.callHistory.flush(true)
|
||||
|
||||
screen.getByText(
|
||||
await screen.findByText(
|
||||
'Why do Fortune 500 companies and top research institutions trust Overleaf to streamline their collaboration? Get in touch to learn more.'
|
||||
)
|
||||
const link = screen.getByRole('button', { name: 'Contact Sales' })
|
||||
|
||||
+42
-31
@@ -19,7 +19,7 @@ import {
|
||||
archiveableProject,
|
||||
copyableProject,
|
||||
} from '../fixtures/projects-data'
|
||||
import * as useLocationModule from '../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
const {
|
||||
@@ -37,7 +37,6 @@ describe('<ProjectListRoot />', function () {
|
||||
this.timeout('10s')
|
||||
|
||||
let sendMBSpy: sinon.SinonSpy
|
||||
let assignStub: sinon.SinonStub
|
||||
|
||||
beforeEach(async function () {
|
||||
global.localStorage.clear()
|
||||
@@ -68,19 +67,14 @@ describe('<ProjectListRoot />', function () {
|
||||
window.metaAttributesCache.set('ol-navbar', {
|
||||
items: [],
|
||||
})
|
||||
assignStub = sinon.stub()
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: assignStub,
|
||||
replace: sinon.stub(),
|
||||
reload: sinon.stub(),
|
||||
setHash: sinon.stub(),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
sendMBSpy.restore()
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
this.locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
})
|
||||
|
||||
describe('welcome page', function () {
|
||||
@@ -136,6 +130,7 @@ describe('<ProjectListRoot />', function () {
|
||||
within(actionsToolbar).getByLabelText('Download')
|
||||
fireEvent.click(downloadButton)
|
||||
|
||||
const assignStub = this.locationWrapperStub.assign
|
||||
await waitFor(() => {
|
||||
expect(assignStub).to.have.been.called
|
||||
})
|
||||
@@ -430,12 +425,15 @@ describe('<ProjectListRoot />', function () {
|
||||
{ repeat: leavableList.length }
|
||||
)
|
||||
|
||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
// + 1 because of select all
|
||||
expect(allCheckboxes.length).to.equal(leavableList.length + 1)
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getAllByRole<HTMLInputElement>('checkbox').length
|
||||
).to.equal(leavableList.length + 1)
|
||||
)
|
||||
|
||||
// first one is the select all checkbox
|
||||
fireEvent.click(allCheckboxes[0])
|
||||
fireEvent.click(screen.getAllByRole('checkbox')[0])
|
||||
|
||||
actionsToolbar = screen.getAllByRole('toolbar')[0]
|
||||
|
||||
@@ -491,9 +489,11 @@ describe('<ProjectListRoot />', function () {
|
||||
{ repeat: deletableList.length }
|
||||
)
|
||||
|
||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
// + 1 because of select all
|
||||
expect(allCheckboxes.length).to.equal(deletableList.length + 1)
|
||||
await waitFor(() => {
|
||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
// + 1 because of select all
|
||||
expect(allCheckboxes.length).to.equal(deletableList.length + 1)
|
||||
})
|
||||
|
||||
// first one is the select all checkbox
|
||||
fireEvent.click(allCheckboxes[0])
|
||||
@@ -563,14 +563,15 @@ describe('<ProjectListRoot />', function () {
|
||||
{ repeat: leavableList.length }
|
||||
)
|
||||
|
||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
// + 1 because of select all
|
||||
expect(allCheckboxes.length).to.equal(
|
||||
deletableAndLeavableList.length + 1
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getAllByRole<HTMLInputElement>('checkbox').length
|
||||
).to.equal(deletableAndLeavableList.length + 1)
|
||||
)
|
||||
|
||||
// first one is the select all checkbox
|
||||
fireEvent.click(allCheckboxes[0])
|
||||
fireEvent.click(screen.getAllByRole<HTMLInputElement>('checkbox')[0])
|
||||
|
||||
actionsToolbar = screen.getAllByRole('toolbar')[0]
|
||||
|
||||
@@ -663,9 +664,11 @@ describe('<ProjectListRoot />', function () {
|
||||
)
|
||||
).to.be.true
|
||||
})
|
||||
expect(
|
||||
screen.queryAllByText(projectsData[index].name)
|
||||
).to.have.length(0)
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryAllByText(projectsData[index].name)
|
||||
).to.have.length(0)
|
||||
})
|
||||
|
||||
screen.getAllByRole('button', {
|
||||
name: `${this.tagName} (${--visibleProjectsCount})`,
|
||||
@@ -699,13 +702,15 @@ describe('<ProjectListRoot />', function () {
|
||||
status: 204,
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
const tagsDropdown = within(actionsToolbar).getByLabelText('Tags')
|
||||
fireEvent.click(tagsDropdown)
|
||||
})
|
||||
const tagsDropdown =
|
||||
await within(actionsToolbar).findByLabelText('Tags')
|
||||
fireEvent.click(tagsDropdown)
|
||||
screen.getByText('Add to tag')
|
||||
|
||||
const newTagButton = screen.getByText('Create new tag')
|
||||
const newTagButton = screen.getByRole('menuitem', {
|
||||
name: 'Create new tag',
|
||||
})
|
||||
|
||||
fireEvent.click(newTagButton)
|
||||
|
||||
const modal = screen.getAllByRole('dialog')[0]
|
||||
@@ -731,7 +736,11 @@ describe('<ProjectListRoot />', function () {
|
||||
})
|
||||
).to.be.true
|
||||
|
||||
screen.getByRole('button', { name: `${this.newTagName} (2)` })
|
||||
await screen.findByRole(
|
||||
'button',
|
||||
{ name: `${this.newTagName} (2)` },
|
||||
{ timeout: 5000 }
|
||||
)
|
||||
})
|
||||
|
||||
it('opens the tags dropdown and remove a tag from selected projects', async function () {
|
||||
@@ -949,7 +958,7 @@ describe('<ProjectListRoot />', function () {
|
||||
).to.be.true
|
||||
|
||||
const table = await screen.findByRole('table')
|
||||
within(table).getByText(newProjectName)
|
||||
await within(table).findByText(newProjectName)
|
||||
expect(within(table).queryByText(oldName)).to.be.null
|
||||
|
||||
const allCheckboxesInTable =
|
||||
@@ -1025,7 +1034,7 @@ describe('<ProjectListRoot />', function () {
|
||||
}
|
||||
)
|
||||
|
||||
screen.getByText(copiedProjectName)
|
||||
await screen.findByText(copiedProjectName)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1073,6 +1082,8 @@ describe('<ProjectListRoot />', function () {
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
|
||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
// first one is the select all checkbox, just check 2 at first
|
||||
fireEvent.click(allCheckboxes[1])
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ describe('Add affiliation widget', function () {
|
||||
await fetchMock.callHistory.flush(true)
|
||||
await waitFor(() => expect(fetchMock.callHistory.called('/api/project')))
|
||||
|
||||
screen.getByText(/are you affiliated with an institution/i)
|
||||
await screen.findByText(/are you affiliated with an institution/i)
|
||||
const addAffiliationLink = screen.getByRole('button', {
|
||||
name: /add affiliation/i,
|
||||
})
|
||||
|
||||
+23
-17
@@ -63,7 +63,7 @@ describe('<TagsList />', function () {
|
||||
const tag1Button = screen.getByText('Tag 1')
|
||||
assert.isFalse(tag1Button.closest('li')?.classList.contains('active'))
|
||||
|
||||
await fireEvent.click(tag1Button)
|
||||
fireEvent.click(tag1Button)
|
||||
assert.isTrue(tag1Button.closest('li')?.classList.contains('active'))
|
||||
})
|
||||
|
||||
@@ -85,7 +85,7 @@ describe('<TagsList />', function () {
|
||||
name: 'New Tag',
|
||||
})
|
||||
|
||||
await fireEvent.click(newTagButton)
|
||||
fireEvent.click(newTagButton)
|
||||
})
|
||||
|
||||
it('modal is open', async function () {
|
||||
@@ -97,7 +97,7 @@ describe('<TagsList />', function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const cancelButton = within(modal).getByRole('button', { name: 'Cancel' })
|
||||
|
||||
await fireEvent.click(cancelButton)
|
||||
fireEvent.click(cancelButton)
|
||||
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
|
||||
})
|
||||
|
||||
@@ -144,13 +144,15 @@ describe('<TagsList />', function () {
|
||||
const createButton = within(modal).getByRole('button', { name: 'Create' })
|
||||
expect(createButton.hasAttribute('disabled')).to.be.false
|
||||
|
||||
await fireEvent.click(createButton)
|
||||
fireEvent.click(createButton)
|
||||
|
||||
await waitFor(
|
||||
() => expect(fetchMock.callHistory.called(`/tag`)).to.be.true
|
||||
)
|
||||
|
||||
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
|
||||
await waitFor(
|
||||
() => expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
|
||||
)
|
||||
|
||||
screen.getByRole('button', {
|
||||
name: 'New Tag (0)',
|
||||
@@ -164,9 +166,9 @@ describe('<TagsList />', function () {
|
||||
const dropdownToggle = within(
|
||||
tag1Button.closest('li') as HTMLElement
|
||||
).getByTestId('tag-dropdown-toggle')
|
||||
await fireEvent.click(dropdownToggle)
|
||||
fireEvent.click(dropdownToggle)
|
||||
const editMenuItem = await screen.findByRole('menuitem', { name: 'Edit' })
|
||||
await fireEvent.click(editMenuItem)
|
||||
fireEvent.click(editMenuItem)
|
||||
})
|
||||
|
||||
it('modal is open', async function () {
|
||||
@@ -178,7 +180,7 @@ describe('<TagsList />', function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const cancelButton = within(modal).getByRole('button', { name: 'Cancel' })
|
||||
|
||||
await fireEvent.click(cancelButton)
|
||||
fireEvent.click(cancelButton)
|
||||
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
|
||||
})
|
||||
|
||||
@@ -237,13 +239,15 @@ describe('<TagsList />', function () {
|
||||
const saveButton = within(modal).getByRole('button', { name: 'Save' })
|
||||
expect(saveButton.hasAttribute('disabled')).to.be.false
|
||||
|
||||
await fireEvent.click(saveButton)
|
||||
fireEvent.click(saveButton)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(fetchMock.callHistory.called(`/tag/abc123def456/rename`))
|
||||
)
|
||||
|
||||
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
|
||||
await waitFor(
|
||||
() => expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
|
||||
)
|
||||
|
||||
screen.getByRole('button', {
|
||||
name: 'New Tag Name (1)',
|
||||
@@ -257,11 +261,11 @@ describe('<TagsList />', function () {
|
||||
const dropdownToggle = within(
|
||||
tag1Button.closest('li') as HTMLElement
|
||||
).getByTestId('tag-dropdown-toggle')
|
||||
await fireEvent.click(dropdownToggle)
|
||||
fireEvent.click(dropdownToggle)
|
||||
const deleteMenuItem = await screen.findByRole('menuitem', {
|
||||
name: 'Delete',
|
||||
})
|
||||
await fireEvent.click(deleteMenuItem)
|
||||
fireEvent.click(deleteMenuItem)
|
||||
})
|
||||
|
||||
it('modal is open', async function () {
|
||||
@@ -273,20 +277,22 @@ describe('<TagsList />', function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const cancelButton = within(modal).getByRole('button', { name: 'Cancel' })
|
||||
|
||||
await fireEvent.click(cancelButton)
|
||||
fireEvent.click(cancelButton)
|
||||
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
|
||||
})
|
||||
|
||||
it('clicking Delete sends a request', async function () {
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const deleteButton = within(modal).getByRole('button', { name: 'Delete' })
|
||||
await fireEvent.click(deleteButton)
|
||||
fireEvent.click(deleteButton)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(fetchMock.callHistory.called(`/tag/bcd234efg567`))
|
||||
)
|
||||
|
||||
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
|
||||
await waitFor(
|
||||
() => expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
|
||||
)
|
||||
expect(
|
||||
screen.queryByRole('button', {
|
||||
name: 'Another Tag (2)',
|
||||
@@ -299,13 +305,13 @@ describe('<TagsList />', function () {
|
||||
|
||||
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
|
||||
const deleteButton = within(modal).getByRole('button', { name: 'Delete' })
|
||||
await fireEvent.click(deleteButton)
|
||||
fireEvent.click(deleteButton)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(fetchMock.callHistory.called(`/tag/bcd234efg567`))
|
||||
)
|
||||
|
||||
within(modal).getByText('Sorry, something went wrong')
|
||||
await within(modal).findByText('Sorry, something went wrong')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ describe('<SystemMessages />', function () {
|
||||
|
||||
await fetchMock.callHistory.flush(true)
|
||||
|
||||
screen.getByText(data.content)
|
||||
await screen.findByText(data.content)
|
||||
expect(screen.queryByRole('button', { name: /close/i })).to.be.null
|
||||
})
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('<SystemMessages />', function () {
|
||||
|
||||
await fetchMock.callHistory.flush(true)
|
||||
|
||||
screen.getByText(data.content)
|
||||
await screen.findByText(data.content)
|
||||
const closeBtn = screen.getByRole('button', { name: /close/i })
|
||||
fireEvent.click(closeBtn)
|
||||
|
||||
|
||||
+9
-15
@@ -2,32 +2,25 @@ import { expect } from 'chai'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import sinon from 'sinon'
|
||||
import { projectsData } from '../../../../fixtures/projects-data'
|
||||
import * as useLocationModule from '../../../../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
import { CompileAndDownloadProjectPDFButtonTooltip } from '../../../../../../../../frontend/js/features/project-list/components/table/cells/action-buttons/compile-and-download-project-pdf-button'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import * as eventTracking from '@/infrastructure/event-tracking'
|
||||
|
||||
describe('<CompileAndDownloadProjectPDFButton />', function () {
|
||||
let assignStub: sinon.SinonStub
|
||||
let locationStub: sinon.SinonStub
|
||||
let sendMBSpy: sinon.SinonSpy
|
||||
|
||||
beforeEach(function () {
|
||||
sendMBSpy = sinon.spy(eventTracking, 'sendMB')
|
||||
assignStub = sinon.stub()
|
||||
locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: assignStub,
|
||||
replace: sinon.stub(),
|
||||
reload: sinon.stub(),
|
||||
setHash: sinon.stub(),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
render(
|
||||
<CompileAndDownloadProjectPDFButtonTooltip project={projectsData[0]} />
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
sendMBSpy.restore()
|
||||
})
|
||||
@@ -57,6 +50,7 @@ describe('<CompileAndDownloadProjectPDFButton />', function () {
|
||||
screen.getByRole('button', { name: 'Compiling…' })
|
||||
})
|
||||
|
||||
const assignStub = this.locationWrapperStub.assign
|
||||
await waitFor(() => {
|
||||
expect(assignStub).to.have.been.called
|
||||
})
|
||||
@@ -85,9 +79,9 @@ describe('<CompileAndDownloadProjectPDFButton />', function () {
|
||||
}) as HTMLButtonElement
|
||||
fireEvent.click(btn)
|
||||
|
||||
await waitFor(() => {
|
||||
screen.getByText(`${projectsData[0].name}: PDF unavailable for download`)
|
||||
})
|
||||
expect(assignStub).to.have.not.been.called
|
||||
await screen.findByText(
|
||||
`${projectsData[0].name}: PDF unavailable for download`
|
||||
)
|
||||
expect(this.locationWrapperStub.assign).to.have.not.been.called
|
||||
})
|
||||
})
|
||||
|
||||
+5
-11
@@ -3,24 +3,17 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import sinon from 'sinon'
|
||||
import { DownloadProjectButtonTooltip } from '../../../../../../../../frontend/js/features/project-list/components/table/cells/action-buttons/download-project-button'
|
||||
import { projectsData } from '../../../../fixtures/projects-data'
|
||||
import * as useLocationModule from '../../../../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
|
||||
describe('<DownloadProjectButton />', function () {
|
||||
let assignStub: sinon.SinonStub
|
||||
|
||||
beforeEach(function () {
|
||||
assignStub = sinon.stub()
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: assignStub,
|
||||
replace: sinon.stub(),
|
||||
reload: sinon.stub(),
|
||||
setHash: sinon.stub(),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
render(<DownloadProjectButtonTooltip project={projectsData[0]} />)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
})
|
||||
|
||||
it('renders tooltip for button', async function () {
|
||||
@@ -35,6 +28,7 @@ describe('<DownloadProjectButton />', function () {
|
||||
}) as HTMLButtonElement
|
||||
fireEvent.click(btn)
|
||||
|
||||
const assignStub = this.locationWrapperStub.assign
|
||||
await waitFor(() => {
|
||||
expect(assignStub).to.have.been.called
|
||||
})
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ describe('<InlineTags />', function () {
|
||||
const removeButton = screen.getByRole('button', {
|
||||
name: 'Remove tag My Test Tag',
|
||||
})
|
||||
await fireEvent.click(removeButton)
|
||||
fireEvent.click(removeButton)
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
fetchMock.callHistory.called(
|
||||
|
||||
+41
-19
@@ -1,4 +1,4 @@
|
||||
import { screen, within, fireEvent } from '@testing-library/react'
|
||||
import { screen, within, fireEvent, waitFor } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import ProjectListTable from '../../../../../../frontend/js/features/project-list/components/table/project-list-table'
|
||||
import { currentProjects } from '../../fixtures/projects-data'
|
||||
@@ -67,9 +67,13 @@ describe('<ProjectListTable />', function () {
|
||||
renderWithProjectListContext(<ProjectListTable />)
|
||||
await fetchMock.callHistory.flush(true)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getAllByRole('row').length).to.equal(
|
||||
currentProjects.length + 1
|
||||
)
|
||||
)
|
||||
const rows = screen.getAllByRole('row')
|
||||
rows.shift() // remove first row since it's the header
|
||||
expect(rows.length).to.equal(currentProjects.length)
|
||||
|
||||
// Project name cell
|
||||
currentProjects.forEach(project => {
|
||||
@@ -139,42 +143,60 @@ describe('<ProjectListTable />', function () {
|
||||
it('selects all projects when header checkbox checked', async function () {
|
||||
renderWithProjectListContext(<ProjectListTable />)
|
||||
await fetchMock.callHistory.flush(true)
|
||||
const checkbox = screen.getByLabelText('Select all projects')
|
||||
const checkbox = await screen.findByLabelText('Select all projects')
|
||||
fireEvent.click(checkbox)
|
||||
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
||||
// + 1 because of select all checkbox
|
||||
expect(allCheckboxesChecked.length).to.equal(currentProjects.length + 1)
|
||||
|
||||
await waitFor(() => {
|
||||
const allCheckboxes = screen.queryAllByRole<HTMLInputElement>('checkbox')
|
||||
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
||||
// + 1 because of select all checkbox
|
||||
expect(allCheckboxesChecked.length).to.equal(currentProjects.length + 1)
|
||||
})
|
||||
})
|
||||
|
||||
it('unselects all projects when select all checkbox uchecked', async function () {
|
||||
renderWithProjectListContext(<ProjectListTable />)
|
||||
await fetchMock.callHistory.flush(true)
|
||||
const checkbox = screen.getByLabelText('Select all projects')
|
||||
const checkbox = await screen.findByLabelText('Select all projects')
|
||||
fireEvent.click(checkbox)
|
||||
fireEvent.click(checkbox)
|
||||
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
||||
expect(allCheckboxesChecked.length).to.equal(0)
|
||||
|
||||
await waitFor(() => {
|
||||
const allCheckboxes = screen.queryAllByRole<HTMLInputElement>('checkbox')
|
||||
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
||||
expect(allCheckboxesChecked.length).to.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
it('unselects select all projects checkbox when one project is unchecked', async function () {
|
||||
renderWithProjectListContext(<ProjectListTable />)
|
||||
await fetchMock.callHistory.flush(true)
|
||||
const checkbox = screen.getByLabelText('Select all projects')
|
||||
const checkbox = await screen.findByLabelText('Select all projects')
|
||||
fireEvent.click(checkbox)
|
||||
let allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
expect(allCheckboxes[1].getAttribute('data-project-id')).to.exist // make sure we are unchecking a project checkbox
|
||||
fireEvent.click(allCheckboxes[1])
|
||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
||||
expect(allCheckboxesChecked.length).to.equal(currentProjects.length - 1)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen
|
||||
.getAllByRole<HTMLInputElement>('checkbox')[1]
|
||||
.getAttribute('data-project-id')
|
||||
).to.exist // make sure we are unchecking a project checkbox
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getAllByRole<HTMLInputElement>('checkbox')[1])
|
||||
|
||||
await waitFor(() => {
|
||||
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
||||
expect(allCheckboxesChecked.length).to.equal(currentProjects.length - 1)
|
||||
})
|
||||
})
|
||||
|
||||
it('only checks the checked project', async function () {
|
||||
renderWithProjectListContext(<ProjectListTable />)
|
||||
await fetchMock.callHistory.flush(true)
|
||||
const checkbox = screen.getByLabelText(`Select ${currentProjects[0].name}`)
|
||||
const checkbox = await screen.findByLabelText(
|
||||
`Select ${currentProjects[0].name}`
|
||||
)
|
||||
fireEvent.click(checkbox)
|
||||
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
||||
|
||||
+18
-12
@@ -1,4 +1,4 @@
|
||||
import { render, screen, within } from '@testing-library/react'
|
||||
import { render, screen, waitFor, within } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import moment from 'moment/moment'
|
||||
import fetchMock from 'fetch-mock'
|
||||
@@ -68,24 +68,30 @@ describe('<ProjectTools />', function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('does not show the Rename option for a project owned by a different user', function () {
|
||||
it('does not show the Rename option for a project owned by a different user', async function () {
|
||||
render(<ProjectListRootInner />)
|
||||
screen.getByLabelText('Select Starfleet Report (readAndWrite)').click()
|
||||
screen.getByRole('button', { name: 'More' }).click()
|
||||
expect(
|
||||
within(
|
||||
screen.getByTestId('project-tools-more-dropdown-menu')
|
||||
).queryByRole('menuitem', { name: 'Rename' })
|
||||
).to.be.null
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(
|
||||
within(
|
||||
screen.getByTestId('project-tools-more-dropdown-menu')
|
||||
).queryByRole('menuitem', { name: 'Rename' })
|
||||
).to.be.null
|
||||
)
|
||||
})
|
||||
|
||||
it('displays the Rename option for a project owned by the current user', function () {
|
||||
it('displays the Rename option for a project owned by the current user', async function () {
|
||||
render(<ProjectListRootInner />)
|
||||
screen.getByLabelText('Select Starfleet Report (owner)').click()
|
||||
screen.getByRole('button', { name: 'More' }).click()
|
||||
within(screen.getByTestId('project-tools-more-dropdown-menu'))
|
||||
.getByRole('menuitem', { name: 'Rename' })
|
||||
.click()
|
||||
within(screen.getByRole('dialog')).getByText('Rename Project')
|
||||
const menu = await screen.findByTestId('project-tools-more-dropdown-menu')
|
||||
const menuItem = within(menu).getByRole('menuitem', {
|
||||
name: 'Rename',
|
||||
})
|
||||
menuItem.click()
|
||||
const dialog = await screen.findByRole('dialog')
|
||||
within(dialog).getByText('Rename Project')
|
||||
})
|
||||
})
|
||||
|
||||
+1
-1
@@ -92,6 +92,6 @@ describe('<RenameProjectModal />', function () {
|
||||
|
||||
await waitFor(() => expect(postRenameMock.callHistory.called()).to.be.true)
|
||||
|
||||
screen.getByText('Something went wrong. Please try again.')
|
||||
await screen.findByText('Something went wrong. Please try again.')
|
||||
})
|
||||
})
|
||||
|
||||
+10
-3
@@ -120,9 +120,16 @@ describe('<AddEmailInput/>', function () {
|
||||
describe('when there is a domain match', function () {
|
||||
beforeEach(async function () {
|
||||
fetchMock.get('express:/institutions/domains', testInstitutionData)
|
||||
fireEvent.change(screen.getByTestId('affiliations-email'), {
|
||||
target: { value: 'user@d' },
|
||||
})
|
||||
const input = await screen.findByTestId('affiliations-email')
|
||||
fireEvent.change(input, { target: { value: 'user@d' } })
|
||||
|
||||
// Wait for the request to complete and the domain cache to pouplate
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(
|
||||
fetchMock.callHistory.called('express:/institutions/domains')
|
||||
).to.be.true
|
||||
)
|
||||
// Wait for component to process the change and update the shadow input
|
||||
await waitFor(() => {
|
||||
const shadowInput = screen.getByTestId(
|
||||
|
||||
+3
-1
@@ -61,7 +61,9 @@ function resetFetchMock() {
|
||||
}
|
||||
|
||||
async function confirmCodeForEmail(email: string) {
|
||||
screen.getByText(`Enter the 6-digit confirmation code sent to ${email}.`)
|
||||
await screen.findByText(
|
||||
`Enter the 6-digit confirmation code sent to ${email}.`
|
||||
)
|
||||
const inputCode = screen.getByLabelText(/6-digit confirmation code/i)
|
||||
fireEvent.change(inputCode, { target: { value: '123456' } })
|
||||
const submitCodeBtn = screen.getByRole<HTMLButtonElement>('button', {
|
||||
|
||||
+6
-4
@@ -139,16 +139,18 @@ describe('user role and institution', function () {
|
||||
departments: [fakeDepartment],
|
||||
})
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: /add role and department/i })
|
||||
)
|
||||
const addRoleButton = await screen.findByRole('button', {
|
||||
name: /add role and department/i,
|
||||
})
|
||||
|
||||
fireEvent.click(addRoleButton)
|
||||
|
||||
await fetchMock.callHistory.flush(true)
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
|
||||
fireEvent.click(screen.getByRole('combobox', { name: /department/i }))
|
||||
|
||||
screen.getByText(fakeDepartment)
|
||||
await screen.findByText(fakeDepartment)
|
||||
})
|
||||
|
||||
it('adds new role and department', async function () {
|
||||
|
||||
+6
-13
@@ -13,7 +13,7 @@ import ReconfirmationInfo from '../../../../../../frontend/js/features/settings/
|
||||
import { ssoUserData } from '../../fixtures/test-user-email-data'
|
||||
import { UserEmailData } from '../../../../../../types/user-email'
|
||||
import { UserEmailsProvider } from '../../../../../../frontend/js/features/settings/context/user-email-context'
|
||||
import * as useLocationModule from '../../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
function renderReconfirmationInfo(data: UserEmailData) {
|
||||
@@ -25,25 +25,18 @@ function renderReconfirmationInfo(data: UserEmailData) {
|
||||
}
|
||||
|
||||
describe('<ReconfirmationInfo/>', function () {
|
||||
let assignStub: sinon.SinonStub
|
||||
|
||||
beforeEach(function () {
|
||||
Object.assign(getMeta('ol-ExposedSettings'), {
|
||||
samlInitPath: '/saml',
|
||||
})
|
||||
fetchMock.get('/user/emails?ensureAffiliation=true', [])
|
||||
assignStub = sinon.stub()
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: assignStub,
|
||||
replace: sinon.stub(),
|
||||
reload: sinon.stub(),
|
||||
setHash: sinon.stub(),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
this.locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
})
|
||||
|
||||
describe('reconfirmed via SAML', function () {
|
||||
@@ -115,9 +108,9 @@ describe('<ReconfirmationInfo/>', function () {
|
||||
await waitFor(() => {
|
||||
expect(confirmButton.disabled).to.be.true
|
||||
})
|
||||
sinon.assert.calledOnce(assignStub)
|
||||
sinon.assert.calledOnce(this.locationWrapperStub.assign)
|
||||
sinon.assert.calledWithMatch(
|
||||
assignStub,
|
||||
this.locationWrapperStub.assign,
|
||||
'/saml/init?university_id=2&reconfirm=/user/settings'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import { fireEvent, screen, render, waitFor } from '@testing-library/react'
|
||||
import fetchMock, { type FetchMock } from 'fetch-mock'
|
||||
|
||||
import LeaveModalForm from '../../../../../../frontend/js/features/settings/components/leave/modal-form'
|
||||
import * as useLocationModule from '../../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
import getMeta from '@/utils/meta'
|
||||
|
||||
describe('<LeaveModalForm />', function () {
|
||||
@@ -51,25 +51,19 @@ describe('<LeaveModalForm />', function () {
|
||||
let setInFlight: sinon.SinonStub
|
||||
let setIsFormValid: sinon.SinonStub
|
||||
let deleteMock: FetchMock
|
||||
let assignStub: sinon.SinonStub
|
||||
|
||||
beforeEach(function () {
|
||||
setInFlight = sinon.stub()
|
||||
setIsFormValid = sinon.stub()
|
||||
deleteMock = fetchMock.post('/user/delete', 200)
|
||||
assignStub = sinon.stub()
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: assignStub,
|
||||
replace: sinon.stub(),
|
||||
reload: sinon.stub(),
|
||||
setHash: sinon.stub(),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
Object.assign(getMeta('ol-ExposedSettings'), { isOverleaf: true })
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
this.locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
})
|
||||
|
||||
it('with valid form', async function () {
|
||||
@@ -86,6 +80,7 @@ describe('<LeaveModalForm />', function () {
|
||||
sinon.assert.calledOnce(setInFlight)
|
||||
sinon.assert.calledWithMatch(setInFlight, true)
|
||||
expect(deleteMock.callHistory.called()).to.be.true
|
||||
const assignStub = this.locationWrapperStub.assign
|
||||
await waitFor(() => {
|
||||
sinon.assert.calledTwice(setInFlight)
|
||||
sinon.assert.calledWithMatch(setInFlight, false)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai'
|
||||
import { renderHook } from '@testing-library/react-hooks'
|
||||
import { renderHook, waitFor } from '@testing-library/react'
|
||||
import {
|
||||
SSOProvider,
|
||||
useSSOContext,
|
||||
@@ -60,10 +60,11 @@ describe('SSOContext', function () {
|
||||
})
|
||||
|
||||
it('should unlink an existing subscription', async function () {
|
||||
const { result, waitForNextUpdate } = renderSSOContext()
|
||||
const { result } = renderSSOContext()
|
||||
result.current.unlink('google')
|
||||
await waitForNextUpdate()
|
||||
expect(result.current.subscriptions.google.linked).to.be.false
|
||||
await waitFor(
|
||||
() => expect(result.current.subscriptions.google.linked).to.be.false
|
||||
)
|
||||
})
|
||||
|
||||
it('when the provider is not linked, should do nothing', function () {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { expect } from 'chai'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { renderHook } from '@testing-library/react-hooks'
|
||||
import { waitFor } from '@testing-library/react'
|
||||
|
||||
import { renderHook, waitFor } from '@testing-library/react'
|
||||
import {
|
||||
EmailContextType,
|
||||
UserEmailsProvider,
|
||||
@@ -53,12 +51,14 @@ describe('UserEmailContext', function () {
|
||||
const { result } = renderUserEmailsContext()
|
||||
await fetchMock.callHistory.flush(true)
|
||||
expect(fetchMock.callHistory.calls()).to.have.lengthOf(1)
|
||||
expect(result.current.state.data.byId).to.deep.equal({
|
||||
'bar@overleaf.com': { ...untrustedUserData, ...confirmedUserData },
|
||||
'baz@overleaf.com': unconfirmedUserData,
|
||||
'foo@overleaf.com': professionalUserData,
|
||||
'qux@overleaf.com': unconfirmedCommonsUserData,
|
||||
})
|
||||
await waitFor(() =>
|
||||
expect(result.current.state.data.byId).to.deep.equal({
|
||||
'bar@overleaf.com': { ...untrustedUserData, ...confirmedUserData },
|
||||
'baz@overleaf.com': unconfirmedUserData,
|
||||
'foo@overleaf.com': professionalUserData,
|
||||
'qux@overleaf.com': unconfirmedCommonsUserData,
|
||||
})
|
||||
)
|
||||
expect(result.current.state.data.linkedInstitutionIds).to.have.lengthOf(0)
|
||||
|
||||
expect(result.current.isInitializing).to.equal(false)
|
||||
@@ -70,10 +70,8 @@ describe('UserEmailContext', function () {
|
||||
const { result } = renderUserEmailsContext()
|
||||
await fetchMock.callHistory.flush()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isInitializing).to.equal(false)
|
||||
expect(result.current.isInitializingError).to.equal(true)
|
||||
})
|
||||
await waitFor(() => expect(result.current.isInitializing).to.equal(false))
|
||||
expect(result.current.isInitializingError).to.equal(true)
|
||||
})
|
||||
|
||||
describe('state.isLoading', function () {
|
||||
@@ -82,12 +80,16 @@ describe('UserEmailContext', function () {
|
||||
expect(result.current.state.isLoading).to.equal(true)
|
||||
})
|
||||
|
||||
it('should be updated with `setLoading`', function () {
|
||||
it('should be updated with `setLoading`', async function () {
|
||||
const { result } = renderUserEmailsContext()
|
||||
result.current.setLoading(true)
|
||||
expect(result.current.state.isLoading).to.equal(true)
|
||||
await waitFor(() =>
|
||||
expect(result.current.state.isLoading).to.equal(true)
|
||||
)
|
||||
result.current.setLoading(false)
|
||||
expect(result.current.state.isLoading).to.equal(false)
|
||||
await waitFor(() =>
|
||||
expect(result.current.state.isLoading).to.equal(false)
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -106,15 +108,19 @@ describe('UserEmailContext', function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('should set `isLoading === true`', function () {
|
||||
fetchMock.get(/\/user\/emails/, [
|
||||
{
|
||||
email: 'new@email.com',
|
||||
default: true,
|
||||
},
|
||||
])
|
||||
it('should set `isLoading === true`', async function () {
|
||||
fetchMock.get(
|
||||
/\/user\/emails/,
|
||||
[
|
||||
{
|
||||
email: 'new@email.com',
|
||||
default: true,
|
||||
},
|
||||
],
|
||||
{ delay: 100 }
|
||||
)
|
||||
result.current.getEmails()
|
||||
expect(result.current.state.isLoading).to.be.true
|
||||
await waitFor(() => expect(result.current.state.isLoading).to.be.true)
|
||||
})
|
||||
|
||||
it('requests a new set of emails', async function () {
|
||||
@@ -125,9 +131,11 @@ describe('UserEmailContext', function () {
|
||||
fetchMock.get(/\/user\/emails/, [emailData])
|
||||
result.current.getEmails()
|
||||
await fetchMock.callHistory.flush(true)
|
||||
expect(result.current.state.data.byId).to.deep.equal({
|
||||
'new@email.com': emailData,
|
||||
})
|
||||
await waitFor(() =>
|
||||
expect(result.current.state.data.byId).to.deep.equal({
|
||||
'new@email.com': emailData,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('should populate `linkedInstitutionIds`', async function () {
|
||||
@@ -138,42 +146,55 @@ describe('UserEmailContext', function () {
|
||||
])
|
||||
const { result } = renderUserEmailsContext()
|
||||
await fetchMock.callHistory.flush(true)
|
||||
expect(result.current.state.data.linkedInstitutionIds).to.deep.equal([
|
||||
'saml_provider_1',
|
||||
'saml_provider_2',
|
||||
])
|
||||
await waitFor(() =>
|
||||
expect(result.current.state.data.linkedInstitutionIds).to.deep.equal([
|
||||
'saml_provider_1',
|
||||
'saml_provider_2',
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('makePrimary()', function () {
|
||||
it('sets an email as `default`', function () {
|
||||
it('sets an email as `default`', async function () {
|
||||
expect(result.current.state.data.byId['bar@overleaf.com'].default).to.be
|
||||
.false
|
||||
result.current.makePrimary('bar@overleaf.com')
|
||||
expect(result.current.state.data.byId['bar@overleaf.com'].default).to.be
|
||||
.true
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(result.current.state.data.byId['bar@overleaf.com'].default)
|
||||
.to.be.true
|
||||
)
|
||||
})
|
||||
|
||||
it('sets `default=false` for the current primary email ', function () {
|
||||
it('sets `default=false` for the current primary email ', async function () {
|
||||
expect(result.current.state.data.byId['foo@overleaf.com'].default).to.be
|
||||
.true
|
||||
result.current.makePrimary('bar@overleaf.com')
|
||||
expect(result.current.state.data.byId['foo@overleaf.com'].default).to.be
|
||||
.false
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(result.current.state.data.byId['foo@overleaf.com'].default)
|
||||
.to.be.false
|
||||
)
|
||||
})
|
||||
|
||||
it('produces no effect when passing a non-existing email', function () {
|
||||
it('produces no effect when passing a non-existing email', async function () {
|
||||
const emails = cloneDeep(result.current.state.data.byId)
|
||||
result.current.makePrimary('non-existing@email.com')
|
||||
expect(result.current.state.data.byId).to.deep.equal(emails)
|
||||
await waitFor(() =>
|
||||
expect(result.current.state.data.byId).to.deep.equal(emails)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteEmail()', function () {
|
||||
it('removes data from the deleted email', function () {
|
||||
it('removes data from the deleted email', async function () {
|
||||
result.current.deleteEmail('bar@overleaf.com')
|
||||
expect(result.current.state.data.byId['bar@overleaf.com']).to.be
|
||||
.undefined
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(result.current.state.data.byId['bar@overleaf.com']).to.be
|
||||
.undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('produces no effect when passing a non-existing email', function () {
|
||||
@@ -184,14 +205,20 @@ describe('UserEmailContext', function () {
|
||||
})
|
||||
|
||||
describe('setEmailAffiliationBeingEdited()', function () {
|
||||
it('sets an email as currently being edited', function () {
|
||||
it('sets an email as currently being edited', async function () {
|
||||
result.current.setEmailAffiliationBeingEdited('bar@overleaf.com')
|
||||
expect(result.current.state.data.emailAffiliationBeingEdited).to.equal(
|
||||
'bar@overleaf.com'
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
result.current.state.data.emailAffiliationBeingEdited
|
||||
).to.equal('bar@overleaf.com')
|
||||
)
|
||||
|
||||
result.current.setEmailAffiliationBeingEdited(null)
|
||||
expect(result.current.state.data.emailAffiliationBeingEdited).to.be.null
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(result.current.state.data.emailAffiliationBeingEdited).to.be
|
||||
.null
|
||||
)
|
||||
})
|
||||
|
||||
it('produces no effect when passing a non-existing email', function () {
|
||||
@@ -202,15 +229,17 @@ describe('UserEmailContext', function () {
|
||||
})
|
||||
|
||||
describe('updateAffiliation()', function () {
|
||||
it('updates affiliation data for an email', function () {
|
||||
it('updates affiliation data for an email', async function () {
|
||||
result.current.updateAffiliation(
|
||||
'foo@overleaf.com',
|
||||
'new role',
|
||||
'new department'
|
||||
)
|
||||
expect(
|
||||
result.current.state.data.byId['foo@overleaf.com'].affiliation!.role
|
||||
).to.equal('new role')
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
result.current.state.data.byId['foo@overleaf.com'].affiliation!.role
|
||||
).to.equal('new role')
|
||||
)
|
||||
expect(
|
||||
result.current.state.data.byId['foo@overleaf.com'].affiliation!
|
||||
.department
|
||||
@@ -317,9 +346,11 @@ describe('UserEmailContext', function () {
|
||||
result.current.deleteEmail(affiliatedEmail1.email)
|
||||
result.current.resetLeaversSurveyExpiration(affiliatedEmail1)
|
||||
|
||||
expect(
|
||||
localStorage.getItem('showInstitutionalLeaversSurveyUntil')
|
||||
).to.be.greaterThan(Date.now())
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
localStorage.getItem('showInstitutionalLeaversSurveyUntil')
|
||||
).to.be.greaterThan(Date.now())
|
||||
)
|
||||
})
|
||||
|
||||
it("when the leaver has no institution license, it shouldn't reset the survey expiration date", async function () {
|
||||
|
||||
+26
-12
@@ -14,15 +14,15 @@ import {
|
||||
USER_EMAIL,
|
||||
USER_ID,
|
||||
} from '../../../helpers/editor-providers'
|
||||
import * as useLocationModule from '../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
|
||||
async function changePrivilegeLevel(screen, { current, next }) {
|
||||
const select = screen.getByDisplayValue(current)
|
||||
await fireEvent.click(select)
|
||||
fireEvent.click(select)
|
||||
const option = screen.getByRole('option', {
|
||||
name: next,
|
||||
})
|
||||
await fireEvent.click(option)
|
||||
fireEvent.click(option)
|
||||
}
|
||||
|
||||
describe('<ShareProjectModal/>', function () {
|
||||
@@ -89,19 +89,17 @@ describe('<ShareProjectModal/>', function () {
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: sinon.stub(),
|
||||
replace: sinon.stub(),
|
||||
reload: sinon.stub(),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
fetchMock.get('/user/contacts', { contacts })
|
||||
window.metaAttributesCache.set('ol-user', { allowedFreeTrial: true })
|
||||
window.metaAttributesCache.set('ol-showUpgradePrompt', true)
|
||||
window.metaAttributesCache.set('ol-isReviewerRoleEnabled', true)
|
||||
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
cleanUpContext()
|
||||
})
|
||||
@@ -199,6 +197,8 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('displays actions for project-owners', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
|
||||
const invites = [
|
||||
{
|
||||
_id: 'invited-author',
|
||||
@@ -304,6 +304,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
|
||||
it('only shows read-only token link to restricted token members', async function () {
|
||||
window.metaAttributesCache.set('ol-isRestrictedTokenMember', true)
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
isRestrictedTokenMember: true,
|
||||
@@ -349,6 +350,8 @@ describe('<ShareProjectModal/>', function () {
|
||||
},
|
||||
]
|
||||
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: {
|
||||
project: {
|
||||
@@ -382,6 +385,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('resends an invite', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.postOnce(
|
||||
'express:/project/:projectId/invite/:inviteId/resend',
|
||||
204
|
||||
@@ -415,10 +419,11 @@ describe('<ShareProjectModal/>', function () {
|
||||
await waitFor(() => expect(closeButton.disabled).to.be.true)
|
||||
|
||||
expect(fetchMock.callHistory.done()).to.be.true
|
||||
expect(closeButton.disabled).to.be.false
|
||||
await waitFor(() => expect(closeButton.disabled).to.be.false)
|
||||
})
|
||||
|
||||
it('revokes an invite', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.deleteOnce('express:/project/:projectId/invite/:inviteId', 204)
|
||||
|
||||
const invites = [
|
||||
@@ -448,10 +453,11 @@ describe('<ShareProjectModal/>', function () {
|
||||
await waitFor(() => expect(closeButton.disabled).to.be.true)
|
||||
|
||||
expect(fetchMock.callHistory.done()).to.be.true
|
||||
expect(closeButton.disabled).to.be.false
|
||||
await waitFor(() => expect(closeButton.disabled).to.be.false)
|
||||
})
|
||||
|
||||
it('changes member privileges to read + write', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.putOnce('express:/project/:projectId/users/:userId', 204)
|
||||
|
||||
const members = [
|
||||
@@ -488,10 +494,11 @@ describe('<ShareProjectModal/>', function () {
|
||||
expect(JSON.parse(body)).to.deep.equal({ privilegeLevel: 'readAndWrite' })
|
||||
|
||||
expect(fetchMock.callHistory.done()).to.be.true
|
||||
expect(closeButton.disabled).to.be.false
|
||||
await waitFor(() => expect(closeButton.disabled).to.be.false)
|
||||
})
|
||||
|
||||
it('removes a member from the project', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.deleteOnce('express:/project/:projectId/users/:userId', 204)
|
||||
|
||||
const members = [
|
||||
@@ -534,6 +541,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('changes member privileges to owner with confirmation', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.postOnce('express:/project/:projectId/transfer-ownership', 204)
|
||||
|
||||
const members = [
|
||||
@@ -582,6 +590,8 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('sends invites to input email addresses', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: {
|
||||
project: {
|
||||
@@ -674,6 +684,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('displays a message when the collaborator limit is reached', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.post(
|
||||
'/event/paywall-prompt',
|
||||
{},
|
||||
@@ -748,6 +759,8 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('handles server error responses', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
scope: {
|
||||
project: {
|
||||
@@ -807,6 +820,7 @@ describe('<ShareProjectModal/>', function () {
|
||||
})
|
||||
|
||||
it('handles switching between access levels', async function () {
|
||||
fetchMock.get(`/project/${project._id}/tokens`, {})
|
||||
fetchMock.post('express:/project/:projectId/settings/admin', 204)
|
||||
|
||||
renderWithEditorContext(<ShareProjectModal {...modalProps} />, {
|
||||
|
||||
+3
-3
@@ -307,7 +307,7 @@ describe('autocomplete', { scrollBehavior: false }, function () {
|
||||
packageNames: new Set(['foo']),
|
||||
}
|
||||
|
||||
const MetadataProvider: FC = ({ children }) => {
|
||||
const MetadataProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<MetadataContext.Provider value={metadata}>
|
||||
{children}
|
||||
@@ -376,7 +376,7 @@ describe('autocomplete', { scrollBehavior: false }, function () {
|
||||
|
||||
const scope = mockScope()
|
||||
|
||||
const ReferencesProvider: FC = ({ children }) => {
|
||||
const ReferencesProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<ReferencesContext.Provider
|
||||
value={{
|
||||
@@ -728,7 +728,7 @@ describe('autocomplete', { scrollBehavior: false }, function () {
|
||||
packageNames: new Set<string>('amsmath'),
|
||||
}
|
||||
|
||||
const MetadataProvider: FC = ({ children }) => {
|
||||
const MetadataProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<MetadataContext.Provider value={metadata}>
|
||||
{children}
|
||||
|
||||
+5
-3
@@ -43,7 +43,9 @@ describe('<FigureModal />', function () {
|
||||
const scope = mockScope(content)
|
||||
scope.editor.showVisual = true
|
||||
|
||||
const FileTreePathProvider: FC = ({ children }) => (
|
||||
const FileTreePathProvider: FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => (
|
||||
<FileTreePathContext.Provider
|
||||
value={{
|
||||
dirname: cy.stub(),
|
||||
@@ -545,7 +547,7 @@ describe('<FigureModal />', function () {
|
||||
cy.findByText('Done').click()
|
||||
cy.get('.cm-content').should(
|
||||
'have.text',
|
||||
'\\begin{figure}\\centering\\end{figure}'
|
||||
'\\begin{figure}\\centering\\includegraphics[width=0.75\\linewidth]{frog.jpg}\\end{figure}'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -567,7 +569,7 @@ describe('<FigureModal />', function () {
|
||||
cy.findByText('Done').click()
|
||||
cy.get('.cm-content').should(
|
||||
'have.text',
|
||||
'\\begin{figure}\\centering🏷fig:my-label\\end{figure}'
|
||||
'\\begin{figure}\\centering\\includegraphics[width=0.75\\linewidth]{frog.jpg}🏷fig:my-label\\end{figure}'
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
+24
-1
@@ -178,7 +178,9 @@ cell 3 & cell 4 \\\\
|
||||
})
|
||||
|
||||
it('Adds and removes borders when theme is changed', function () {
|
||||
// Add a blank line above the table to allow room for the table toolbar
|
||||
mountEditor(`
|
||||
|
||||
\\begin{tabular}{c|c}
|
||||
cell 1 & cell 2 \\\\
|
||||
cell 3 & cell 4 \\\\
|
||||
@@ -216,7 +218,9 @@ cell 3 & cell 4 \\\\
|
||||
})
|
||||
|
||||
it('Changes the column alignment with dropdown buttons', function () {
|
||||
// Add a blank line above the table to allow room for the table toolbar
|
||||
mountEditor(`
|
||||
|
||||
\\begin{tabular}{cc}
|
||||
cell 1 & cell 2 \\\\
|
||||
cell 3 & cell 4 \\\\
|
||||
@@ -269,7 +273,9 @@ cell 3 & cell 4 \\\\
|
||||
})
|
||||
|
||||
it('Removes rows and columns', function () {
|
||||
// Add a blank line above the table to allow room for the table toolbar
|
||||
mountEditor(`
|
||||
|
||||
\\begin{tabular}{|c|c|c|}
|
||||
\\hline
|
||||
cell 1 & cell 2 & cell 3 \\\\ \\hline
|
||||
@@ -304,7 +310,9 @@ cell 3 & cell 4 \\\\
|
||||
})
|
||||
|
||||
it('Removes rows correctly when removing from the left', function () {
|
||||
// Add a blank line above the table to allow room for the table toolbar
|
||||
mountEditor(`
|
||||
|
||||
\\begin{tabular}{|c|c|c|}\\hline
|
||||
cell 1&cell 2&cell 3 \\\\\\hline
|
||||
\\end{tabular}
|
||||
@@ -324,7 +332,9 @@ cell 3 & cell 4 \\\\
|
||||
})
|
||||
|
||||
it('Merges and unmerged cells', function () {
|
||||
// Add a blank line above the table to allow room for the table toolbar
|
||||
mountEditor(`
|
||||
|
||||
\\begin{tabular}{ccc}
|
||||
cell 1 & cell 2 & cell 3 \\\\
|
||||
cell 4 & cell 5 & cell 6 \\\\
|
||||
@@ -333,6 +343,7 @@ cell 3 & cell 4 \\\\
|
||||
cy.get('.table-generator-cell').first().click()
|
||||
cy.get('.table-generator-cell').first().type('{shift}{rightarrow}')
|
||||
cy.get('.table-generator-floating-toolbar').as('toolbar').should('exist')
|
||||
cy.get('@toolbar').scrollIntoView()
|
||||
cy.get('@toolbar').findByLabelText('Merge cells').click()
|
||||
checkTable([
|
||||
[{ text: 'cell 1 cell 2', colspan: 2 }, 'cell 3'],
|
||||
@@ -346,7 +357,9 @@ cell 3 & cell 4 \\\\
|
||||
})
|
||||
|
||||
it('Adds rows and columns', function () {
|
||||
// Add a blank line above the table to allow room for the table toolbar
|
||||
mountEditor(`
|
||||
|
||||
\\begin{tabular}{c}
|
||||
cell 1
|
||||
\\end{tabular}
|
||||
@@ -398,7 +411,9 @@ cell 3 & cell 4 \\\\
|
||||
})
|
||||
|
||||
it('Removes the table on toolbar button click', function () {
|
||||
// Add a blank line above the table to allow room for the table toolbar
|
||||
mountEditor(`
|
||||
|
||||
\\begin{tabular}{c}
|
||||
cell 1
|
||||
\\end{tabular}`)
|
||||
@@ -409,7 +424,9 @@ cell 3 & cell 4 \\\\
|
||||
})
|
||||
|
||||
it('Moves the caption when using dropdown', function () {
|
||||
// Add a blank line above the table to allow room for the table toolbar
|
||||
mountEditor(`
|
||||
|
||||
\\begin{table}
|
||||
\\caption{Table caption}
|
||||
\\label{tab:table}
|
||||
@@ -456,7 +473,9 @@ cell 3 & cell 4 \\\\
|
||||
})
|
||||
|
||||
it('Renders a table with custom column spacing', function () {
|
||||
// Add a blank line above the table to allow room for the table toolbar
|
||||
mountEditor(`
|
||||
|
||||
\\begin{tabular}{@{}c@{}l!{}}
|
||||
cell 1 & cell 2 \\\\
|
||||
cell 3 & cell 4 \\\\
|
||||
@@ -498,8 +517,10 @@ cell 3 & cell 4 \\\\
|
||||
|
||||
describe('Fixed width columns', function () {
|
||||
it('Can add fixed width columns', function () {
|
||||
// Add a blank line above the table to allow room for the table toolbar
|
||||
// Check that no column indicators exist
|
||||
mountEditor(`
|
||||
|
||||
\\begin{tabular}{cc}
|
||||
cell 1 & cell 2\\\\
|
||||
cell 3 & cell 4 \\\\
|
||||
@@ -574,9 +595,11 @@ cell 3 & cell 4 \\\\
|
||||
}
|
||||
)
|
||||
|
||||
it(`It can justify fixed width cells`, function () {
|
||||
it(`Can justify fixed width cells`, function () {
|
||||
// Add a blank line above the table to allow room for the table toolbar
|
||||
// Check that no column indicators exist
|
||||
mountEditor(`
|
||||
|
||||
\\begin{tabular}{>{\\raggedright\\arraybackslash}p{2cm}c}
|
||||
cell 1 & cell 2\\\\
|
||||
cell 3 & cell 4 \\\\
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import { FileTreePathContext } from '@/features/file-tree/contexts/file-tree-pat
|
||||
import { TestContainer } from '../helpers/test-container'
|
||||
import { PermissionsContext } from '@/features/ide-react/context/permissions-context'
|
||||
|
||||
const FileTreePathProvider: FC = ({ children }) => (
|
||||
const FileTreePathProvider: FC<React.PropsWithChildren> = ({ children }) => (
|
||||
<FileTreePathContext.Provider
|
||||
value={{
|
||||
dirname: cy.stub(),
|
||||
@@ -22,7 +22,7 @@ const FileTreePathProvider: FC = ({ children }) => (
|
||||
</FileTreePathContext.Provider>
|
||||
)
|
||||
|
||||
const PermissionsProvider: FC = ({ children }) => (
|
||||
const PermissionsProvider: FC<React.PropsWithChildren> = ({ children }) => (
|
||||
<PermissionsContext.Provider
|
||||
value={{
|
||||
read: true,
|
||||
|
||||
+3
-1
@@ -21,7 +21,9 @@ describe('<CodeMirrorEditor/> in Visual mode', function () {
|
||||
const scope = mockScope(content)
|
||||
scope.editor.showVisual = true
|
||||
|
||||
const FileTreePathProvider: FC = ({ children }) => (
|
||||
const FileTreePathProvider: FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => (
|
||||
<FileTreePathContext.Provider
|
||||
value={{
|
||||
dirname: cy.stub(),
|
||||
|
||||
+1
-1
@@ -216,7 +216,7 @@ describe('<CodeMirrorEditor/>', { scrollBehavior: false }, function () {
|
||||
onlineUsersCount: 3,
|
||||
}
|
||||
|
||||
const OnlineUsersProvider: FC = ({ children }) => {
|
||||
const OnlineUsersProvider: FC<React.PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<OnlineUsersContext.Provider value={value}>
|
||||
{children}
|
||||
|
||||
+5
-11
@@ -15,7 +15,7 @@ import {
|
||||
groupActiveSubscription,
|
||||
groupActiveSubscriptionWithPendingLicenseChange,
|
||||
} from '../../fixtures/subscriptions'
|
||||
import * as useLocationModule from '../../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
import { UserId } from '../../../../../../types/user'
|
||||
import { SplitTestProvider } from '@/shared/context/split-test-context'
|
||||
|
||||
@@ -73,16 +73,9 @@ describe('<GroupSubscriptionMemberships />', function () {
|
||||
})
|
||||
|
||||
describe('opens leave group modal when button is clicked', function () {
|
||||
let reloadStub: sinon.SinonStub
|
||||
|
||||
beforeEach(function () {
|
||||
reloadStub = sinon.stub()
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: sinon.stub(),
|
||||
replace: sinon.stub(),
|
||||
reload: reloadStub,
|
||||
setHash: sinon.stub(),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
|
||||
render(
|
||||
<SplitTestProvider>
|
||||
@@ -111,7 +104,7 @@ describe('<GroupSubscriptionMemberships />', function () {
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
})
|
||||
|
||||
it('close the modal', function () {
|
||||
@@ -130,6 +123,7 @@ describe('<GroupSubscriptionMemberships />', function () {
|
||||
fireEvent.click(this.leaveNowButton)
|
||||
|
||||
expect(leaveGroupApiMock.callHistory.called()).to.be.true
|
||||
const reloadStub = this.locationWrapperStub.reload
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
})
|
||||
|
||||
+2
-2
@@ -97,7 +97,7 @@ describe('<ManagedInstitutions />', function () {
|
||||
)
|
||||
|
||||
const unsubscribeLink = screen.getByText('Unsubscribe')
|
||||
await fireEvent.click(unsubscribeLink)
|
||||
fireEvent.click(unsubscribeLink)
|
||||
await waitFor(
|
||||
() => expect(fetchMock.callHistory.called(unsubscribeUrl)).to.be.true
|
||||
)
|
||||
@@ -126,7 +126,7 @@ describe('<ManagedInstitutions />', function () {
|
||||
|
||||
const subscribeLink = await screen.findByText('Subscribe')
|
||||
|
||||
await fireEvent.click(subscribeLink)
|
||||
fireEvent.click(subscribeLink)
|
||||
await waitFor(
|
||||
() => expect(fetchMock.callHistory.called(subscribeUrl)).to.be.true
|
||||
)
|
||||
|
||||
+8
-13
@@ -9,7 +9,7 @@ import {
|
||||
trialSubscription,
|
||||
} from '../../fixtures/subscriptions'
|
||||
import { renderActiveSubscription } from '../../helpers/render-active-subscription'
|
||||
import * as useLocationModule from '../../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
import { MetaTag } from '@/utils/meta'
|
||||
|
||||
const pauseSubscriptionSplitTestMeta: MetaTag[] = [
|
||||
@@ -45,22 +45,17 @@ function clickSubmitButton() {
|
||||
|
||||
describe('<PauseSubscriptionModal />', function () {
|
||||
beforeEach(function () {
|
||||
reloadStub = sinon.stub()
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: sinon.stub(),
|
||||
replace: sinon.stub(),
|
||||
reload: reloadStub,
|
||||
setHash: sinon.stub(),
|
||||
toString: sinon
|
||||
.stub()
|
||||
.returns('https://www.dev-overleaf.com/user/subscription'),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
this.locationWrapperStub.toString.returns(
|
||||
'https://www.dev-overleaf.com/user/subscription'
|
||||
)
|
||||
this.replaceStateStub = sinon.stub(window.history, 'replaceState')
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
this.locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
this.replaceStateStub.restore()
|
||||
})
|
||||
|
||||
@@ -89,7 +84,6 @@ describe('<PauseSubscriptionModal />', function () {
|
||||
clickCancelButton()
|
||||
await screen.findByText('Pause instead, to pick up where you left off')
|
||||
})
|
||||
let reloadStub: sinon.SinonStub
|
||||
|
||||
it('renders options for pause duration', async function () {
|
||||
renderSubscriptionWithPauseSupport()
|
||||
@@ -135,6 +129,7 @@ describe('<PauseSubscriptionModal />', function () {
|
||||
renderSubscriptionWithPauseSupport()
|
||||
clickCancelButton()
|
||||
clickSubmitButton()
|
||||
const reloadStub = this.locationWrapperStub.reload
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
})
|
||||
|
||||
+10
-15
@@ -4,6 +4,7 @@ import {
|
||||
fireEvent,
|
||||
waitForElementToBeRemoved,
|
||||
within,
|
||||
waitFor,
|
||||
} from '@testing-library/react'
|
||||
import PersonalSubscription from '../../../../../../frontend/js/features/subscription/components/dashboard/personal-subscription'
|
||||
import {
|
||||
@@ -19,7 +20,7 @@ import {
|
||||
import { reactivateSubscriptionUrl } from '../../../../../../frontend/js/features/subscription/data/subscription-url'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import sinon from 'sinon'
|
||||
import * as useLocationModule from '../../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
|
||||
describe('<PersonalSubscription />', function () {
|
||||
afterEach(function () {
|
||||
@@ -48,21 +49,13 @@ describe('<PersonalSubscription />', function () {
|
||||
})
|
||||
|
||||
describe('subscription states ', function () {
|
||||
let reloadStub: sinon.SinonStub
|
||||
|
||||
beforeEach(function () {
|
||||
reloadStub = sinon.stub()
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: sinon.stub(),
|
||||
replace: sinon.stub(),
|
||||
reload: reloadStub,
|
||||
setHash: sinon.stub(),
|
||||
toString: sinon.stub(),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
})
|
||||
|
||||
it('renders the active dash', function () {
|
||||
@@ -107,15 +100,17 @@ describe('<PersonalSubscription />', function () {
|
||||
fireEvent.click(reactivateBtn)
|
||||
expect(reactivateBtn.disabled).to.be.true
|
||||
await fetchMock.callHistory.flush(true)
|
||||
expect(reactivateBtn.disabled).to.be.false
|
||||
expect(reloadStub).not.to.have.been.called
|
||||
await waitFor(() => expect(reactivateBtn.disabled).to.be.false)
|
||||
expect(this.locationWrapperStub.reload).not.to.have.been.called
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
|
||||
// 2nd click - success
|
||||
fetchMock.postOnce(reactivateSubscriptionUrl, 200)
|
||||
fireEvent.click(reactivateBtn)
|
||||
await fetchMock.callHistory.flush(true)
|
||||
expect(reloadStub).to.have.been.calledOnce
|
||||
await waitFor(() => {
|
||||
expect(this.locationWrapperStub.reload).to.have.been.calledOnce
|
||||
})
|
||||
expect(reactivateBtn.disabled).to.be.true
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
+18
-16
@@ -21,7 +21,7 @@ import {
|
||||
extendTrialUrl,
|
||||
subscriptionUpdateUrl,
|
||||
} from '@/features/subscription/data/subscription-url'
|
||||
import * as useLocationModule from '../../../../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
import { MetaTag } from '@/utils/meta'
|
||||
|
||||
describe('<ActiveSubscription />', function () {
|
||||
@@ -77,9 +77,13 @@ describe('<ActiveSubscription />', function () {
|
||||
screen.getByText('You are currently subscribed to the', { exact: false })
|
||||
|
||||
await screen.findByRole('heading', { name: 'Change plan' })
|
||||
expect(
|
||||
screen.getAllByRole('button', { name: 'Change to this plan' }).length > 0
|
||||
).to.be.true
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(
|
||||
screen.getAllByRole('button', { name: 'Change to this plan' })
|
||||
.length > 0
|
||||
).to.be.true
|
||||
)
|
||||
})
|
||||
|
||||
it('notes when user is changing plan at end of current plan term', function () {
|
||||
@@ -193,21 +197,13 @@ describe('<ActiveSubscription />', function () {
|
||||
})
|
||||
|
||||
describe('cancel plan', function () {
|
||||
const assignStub = sinon.stub()
|
||||
const reloadStub = sinon.stub()
|
||||
|
||||
beforeEach(function () {
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: assignStub,
|
||||
replace: sinon.stub(),
|
||||
reload: reloadStub,
|
||||
setHash: sinon.stub(),
|
||||
toString: sinon.stub(),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
@@ -277,6 +273,7 @@ describe('<ActiveSubscription />', function () {
|
||||
name: 'Cancel my subscription',
|
||||
})
|
||||
fireEvent.click(button)
|
||||
const assignStub = this.locationWrapperStub.assign
|
||||
await waitFor(() => {
|
||||
expect(assignStub).to.have.been.called
|
||||
})
|
||||
@@ -406,6 +403,7 @@ describe('<ActiveSubscription />', function () {
|
||||
})
|
||||
fireEvent.click(extendTrialButton)
|
||||
// page is reloaded on success
|
||||
const reloadStub = this.locationWrapperStub.reload
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
})
|
||||
@@ -499,7 +497,10 @@ describe('<ActiveSubscription />', function () {
|
||||
const endPointResponse = {
|
||||
status: 200,
|
||||
}
|
||||
fetchMock.post(subscriptionUpdateUrl, endPointResponse)
|
||||
fetchMock.post(
|
||||
`${subscriptionUpdateUrl}?downgradeToPaidPersonal`,
|
||||
endPointResponse
|
||||
)
|
||||
renderActiveSubscription(monthlyActiveCollaborator)
|
||||
showConfirmCancelUI()
|
||||
const downgradeButton = await screen.findByRole('button', {
|
||||
@@ -507,6 +508,7 @@ describe('<ActiveSubscription />', function () {
|
||||
})
|
||||
fireEvent.click(downgradeButton)
|
||||
// page is reloaded on success
|
||||
const reloadStub = this.locationWrapperStub.reload
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
})
|
||||
|
||||
+8
-13
@@ -20,26 +20,18 @@ import {
|
||||
subscriptionUpdateUrl,
|
||||
} from '../../../../../../../../../frontend/js/features/subscription/data/subscription-url'
|
||||
import { renderActiveSubscription } from '../../../../../helpers/render-active-subscription'
|
||||
import * as useLocationModule from '../../../../../../../../../frontend/js/shared/hooks/use-location'
|
||||
import { location } from '@/shared/components/location'
|
||||
|
||||
describe('<ChangePlanModal />', function () {
|
||||
let reloadStub: sinon.SinonStub
|
||||
|
||||
beforeEach(function () {
|
||||
reloadStub = sinon.stub()
|
||||
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
|
||||
assign: sinon.stub(),
|
||||
replace: sinon.stub(),
|
||||
reload: reloadStub,
|
||||
setHash: sinon.stub(),
|
||||
toString: sinon.stub(),
|
||||
})
|
||||
this.locationWrapperSandbox = sinon.createSandbox()
|
||||
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
cleanUpContext()
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
this.locationStub.restore()
|
||||
this.locationWrapperSandbox.restore()
|
||||
})
|
||||
|
||||
it('renders the individual plans table and group plans UI', async function () {
|
||||
@@ -210,6 +202,7 @@ describe('<ChangePlanModal />', function () {
|
||||
screen.getByRole('button', { name: 'Processing…' })
|
||||
|
||||
// page is reloaded on success
|
||||
const reloadStub = this.locationWrapperStub.reload
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
})
|
||||
@@ -299,6 +292,7 @@ describe('<ChangePlanModal />', function () {
|
||||
screen.getByRole('button', { name: 'Processing…' })
|
||||
|
||||
// page is reloaded on success
|
||||
const reloadStub = this.locationWrapperStub.reload
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
})
|
||||
@@ -530,7 +524,8 @@ describe('<ChangePlanModal />', function () {
|
||||
|
||||
screen.getByRole('button', { name: 'Processing…' })
|
||||
|
||||
// // page is reloaded on success
|
||||
// page is reloaded on success
|
||||
const reloadStub = this.locationWrapperStub.reload
|
||||
await waitFor(() => {
|
||||
expect(reloadStub).to.have.been.called
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { renderHook, act } from '@testing-library/react-hooks'
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import useAsync from '../../../../frontend/js/shared/hooks/use-async'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import sinon from 'sinon'
|
||||
import { renderHook } from '@testing-library/react-hooks'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import useCallbackHandlers from '../../../../frontend/js/shared/hooks/use-callback-handlers'
|
||||
|
||||
describe('useCallbackHandlers', function () {
|
||||
|
||||
Reference in New Issue
Block a user