[web] Update fetch-mock to version 12 (#24837)

* Update fetch-mock to version 12

* Replace `fetchMock.done` by `fetchMock.callHistory.done`

* Replace `…Mock.called` by `…Mock.callHistory.called`

* Replace `fetchMock.reset` by `fetchMock.hardReset`

* Replace `fetchMock.restore` by `fetchMock.hardReset`

* Replace `fetchMock.resetHistory` by `fetchMock.clearHistory`

* Replace `fetchMock.calls` by `fetchMock.callHistory.calls`

* Replace `fetchMock.flush` by `fetchMock.callHistory.flush`

* Update tests for fetch-mock version 12

See https://www.wheresrhys.co.uk/fetch-mock/docs/Usage/upgrade-guide

* Update stories for fetch-mock version 12

* Remove `overwriteRoutes` option

* Add `fetchMock.spyGlobal()` to storybook

* Remove deprecated `sendAsJson` param

* Replace `fetchMock.hardReset()` by `fetchMock.removeRoutes().clearHistory()`

* Fixup fetch-mock in storybook:

Call `mockGlobal` inside the hook, call `removeRoutes` and `unmockGlobal` on cleanup

Behaviour can be tested by navigating between

https://storybook.dev-overleaf.com/main/?path=/story/editor-ai-error-assistant-compile-log-entries--first-log-entry
https://storybook.dev-overleaf.com/main/?path=/story/editor-ai-error-assistant-compile-log-entries--rate-limited

https://storybook.dev-overleaf.com/main/?path=/story/project-list-notifications--project-invite
https://storybook.dev-overleaf.com/main/?path=/story/project-list-notifications--project-invite-network-error

And clicking the buttons

GitOrigin-RevId: 35611b4430259e4c21c3d819ad18b2e6dab66242
This commit is contained in:
Antoine Clausse
2025-04-17 08:06:24 +00:00
committed by Copybot
parent fa62529d82
commit b901bb6c75
99 changed files with 728 additions and 729 deletions
@@ -158,11 +158,9 @@ ImageFile.args = {
export const TextFile = args => {
useFetchMock(fetchMock =>
setupFetchMock(fetchMock).get(
'express:/project/:project_id/blob/:hash',
{ body: bodies.text },
{ overwriteRoutes: true }
)
setupFetchMock(fetchMock).get('express:/project/:project_id/blob/:hash', {
body: bodies.text,
})
)
return <FileView {...args} />
}
@@ -180,11 +178,9 @@ TextFile.args = {
export const UploadedFile = args => {
useFetchMock(fetchMock =>
setupFetchMock(fetchMock).head(
'express:/project/:project_id/blob/:hash',
{ status: 500 },
{ overwriteRoutes: true }
)
setupFetchMock(fetchMock).head('express:/project/:project_id/blob/:hash', {
status: 500,
})
)
return <FileView {...args} />
}
@@ -58,7 +58,7 @@ export const mockCompile = (fetchMock, delay = 1000) =>
outputFiles: cloneDeep(outputFiles),
},
},
{ delay, overwriteRoutes: true }
{ delay }
)
export const mockCompileError = (fetchMock, status = 'success', delay = 1000) =>
@@ -91,27 +91,24 @@ export const mockCompileValidationIssues = (
},
}
},
{ delay, overwriteRoutes: true }
{ delay }
)
export const mockClearCache = fetchMock =>
fetchMock.delete('express:/project/:projectId/output', 204, {
delay: 1000,
overwriteRoutes: true,
})
export const mockBuildFile = fetchMock =>
fetchMock.get(
'express:/build/:file',
(url, options, request) => {
const { pathname } = new URL(url, 'https://example.com')
fetchMock.get('express:/build/:file', (url, options, request) => {
const { pathname } = new URL(url, 'https://example.com')
switch (pathname) {
case '/build/output.blg':
return 'This is BibTeX, Version 4.0' // FIXME
switch (pathname) {
case '/build/output.blg':
return 'This is BibTeX, Version 4.0' // FIXME
case '/build/output.log':
return `
case '/build/output.log':
return `
The LaTeX compiler output
* With a lot of details
@@ -134,31 +131,29 @@ LaTeX Font Info: External font \`cmex10' loaded for size
`
case '/build/output.pdf':
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.addEventListener('load', () => {
resolve({
status: 200,
headers: {
'Content-Length': xhr.getResponseHeader('Content-Length'),
'Content-Type': xhr.getResponseHeader('Content-Type'),
},
body: xhr.response,
})
case '/build/output.pdf':
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.addEventListener('load', () => {
resolve({
status: 200,
headers: {
'Content-Length': xhr.getResponseHeader('Content-Length'),
'Content-Type': xhr.getResponseHeader('Content-Type'),
},
body: xhr.response,
})
xhr.open('GET', examplePdf)
xhr.responseType = 'arraybuffer'
xhr.send()
})
xhr.open('GET', examplePdf)
xhr.responseType = 'arraybuffer'
xhr.send()
})
default:
console.log(pathname)
return 404
}
},
{ sendAsJson: false, overwriteRoutes: true }
)
default:
console.log(pathname)
return 404
}
})
const mockHighlights = [
{
@@ -195,29 +190,25 @@ export const mockEventTracking = fetchMock =>
fetchMock.get('express:/event/:event', 204)
export const mockValidPdf = fetchMock =>
fetchMock.get(
'express:/build/output.pdf',
(url, options, request) => {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.addEventListener('load', () => {
resolve({
status: 200,
headers: {
'Content-Length': xhr.getResponseHeader('Content-Length'),
'Content-Type': xhr.getResponseHeader('Content-Type'),
'Accept-Ranges': 'bytes',
},
body: xhr.response,
})
fetchMock.get('express:/build/output.pdf', (url, options, request) => {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.addEventListener('load', () => {
resolve({
status: 200,
headers: {
'Content-Length': xhr.getResponseHeader('Content-Length'),
'Content-Type': xhr.getResponseHeader('Content-Type'),
'Accept-Ranges': 'bytes',
},
body: xhr.response,
})
xhr.open('GET', examplePdf)
xhr.responseType = 'arraybuffer'
xhr.send()
})
},
{ sendAsJson: false, overwriteRoutes: true }
)
xhr.open('GET', examplePdf)
xhr.responseType = 'arraybuffer'
xhr.send()
})
})
export const mockSynctex = fetchMock =>
fetchMock
@@ -1,18 +1,19 @@
import { useLayoutEffect } from 'react'
import fetchMock from 'fetch-mock'
fetchMock.config.fallbackToNetwork = true
/**
* Run callback to mock fetch routes, call restore() when unmounted
* Run callback to mock fetch routes, call removeRoutes() and unmockGlobal() when unmounted
*/
export default function useFetchMock(
callback: (value: typeof fetchMock) => void
) {
fetchMock.mockGlobal()
useLayoutEffect(() => {
callback(fetchMock)
return () => {
fetchMock.restore()
fetchMock.removeRoutes()
fetchMock.unmockGlobal()
}
}, [callback])
}
@@ -37,9 +37,7 @@ export const ErrorImportingFileFromExternalURL = args => {
useFetchMock(fetchMock => {
mockCreateFileModalFetch(fetchMock)
fetchMock.post('express:/project/:projectId/linked_file', 500, {
overwriteRoutes: true,
})
fetchMock.post('express:/project/:projectId/linked_file', 500)
})
getMeta('ol-ExposedSettings').hasLinkUrlFeature = true
@@ -52,9 +50,7 @@ export const ErrorImportingFileFromReferenceProvider = args => {
useFetchMock(fetchMock => {
mockCreateFileModalFetch(fetchMock)
fetchMock.post('express:/project/:projectId/linked_file', 500, {
overwriteRoutes: true,
})
fetchMock.post('express:/project/:projectId/linked_file', 500)
})
return <FileTreeModalCreateFile {...args} />
@@ -315,11 +315,7 @@ export const OverlayedWithCustomClass = (args: Args) => {
export const SuccessFlow = (args: Args) => {
console.log('.....render')
fetchMock.post(
'express:/test-success',
{ status: 200 },
{ delay: 250, overwriteRoutes: true }
)
fetchMock.post('express:/test-success', { status: 200 }, { delay: 250 })
const { isLoading, isSuccess, runAsync } = useAsync()
function handleClick() {
@@ -1,5 +1,5 @@
import { merge, cloneDeep } from 'lodash'
import { FetchMockStatic } from 'fetch-mock'
import { type FetchMock } from 'fetch-mock'
import { UserEmailData } from '../../../../types/user-email'
import {
Institution,
@@ -32,7 +32,7 @@ export const fakeReconfirmationUsersData = {
default: false,
} as DeepReadonly<UserEmailData>
export function defaultSetupMocks(fetchMock: FetchMockStatic) {
export function defaultSetupMocks(fetchMock: FetchMock) {
// at least one project is required to show some notifications
const projects = [{}] as Project[]
fetchMock.post(/\/api\/project/, {
@@ -54,7 +54,7 @@ export function setDefaultMeta() {
window.metaAttributesCache.set('ol-userEmails', [])
}
export function errorsMocks(fetchMock: FetchMockStatic) {
export function errorsMocks(fetchMock: FetchMock) {
defaultSetupMocks(fetchMock)
fetchMock.post(/\/user\/emails\/*/, 500, { delay: MOCK_DELAY })
fetchMock.post(
@@ -74,7 +74,7 @@ export function setInstitutionMeta(institutionData: Partial<Institution>) {
])
}
export function institutionSetupMocks(fetchMock: FetchMockStatic) {
export function institutionSetupMocks(fetchMock: FetchMock) {
defaultSetupMocks(fetchMock)
fetchMock.delete(/\/notifications\/*/, 200, { delay: MOCK_DELAY })
}
@@ -84,7 +84,7 @@ export function setCommonMeta(notificationData: DeepPartial<Notification>) {
window.metaAttributesCache.set('ol-notifications', [notificationData])
}
export function commonSetupMocks(fetchMock: FetchMockStatic) {
export function commonSetupMocks(fetchMock: FetchMock) {
defaultSetupMocks(fetchMock)
fetchMock.post(
/\/project\/[A-Za-z0-9]+\/invite\/token\/[A-Za-z0-9]+\/accept/,
@@ -98,7 +98,7 @@ export function setReconfirmationMeta() {
window.metaAttributesCache.set('ol-userEmails', [fakeReconfirmationUsersData])
}
export function reconfirmationSetupMocks(fetchMock: FetchMockStatic) {
export function reconfirmationSetupMocks(fetchMock: FetchMock) {
defaultSetupMocks(fetchMock)
fetchMock.post(/\/user\/emails\/resend_confirmation/, 200, {
delay: MOCK_DELAY,
@@ -113,19 +113,15 @@ export function setReconfirmAffiliationMeta() {
)
}
export function reconfirmAffiliationSetupMocks(fetchMock: FetchMockStatic) {
export function reconfirmAffiliationSetupMocks(fetchMock: FetchMock) {
defaultSetupMocks(fetchMock)
fetchMock.post(
/\/api\/project/,
{
status: 200,
body: {
projects: [{}],
totalSize: 0,
},
fetchMock.post(/\/api\/project/, {
status: 200,
body: {
projects: [{}],
totalSize: 0,
},
{ overwriteRoutes: true }
)
})
fetchMock.post(/\/user\/emails\/send-reconfirmation/, 200, {
delay: MOCK_DELAY,
})
@@ -1,9 +1,9 @@
import SystemMessages from '@/shared/components/system-messages'
import useFetchMock from '../hooks/use-fetch-mock'
import { FetchMockStatic } from 'fetch-mock'
import { type FetchMock } from 'fetch-mock'
export const SystemMessage = (args: any) => {
useFetchMock((fetchMock: FetchMockStatic) => {
useFetchMock((fetchMock: FetchMock) => {
fetchMock.get(/\/system\/messages/, [
{
_id: 1,
@@ -23,7 +23,7 @@ export const SystemMessage = (args: any) => {
}
export const TranslationMessage = (args: any) => {
useFetchMock((fetchMock: FetchMockStatic) => {
useFetchMock((fetchMock: FetchMock) => {
fetchMock.get(/\/system\/messages/, [])
})
@@ -174,7 +174,6 @@ export function reconfirmationSetupMocks(fetchMock) {
defaultSetupMocks(fetchMock)
fetchMock.get(/\/user\/emails/, fakeReconfirmationUsersData, {
delay: MOCK_DELAY,
overwriteRoutes: true,
})
}
@@ -186,7 +185,6 @@ export function emailLimitSetupMocks(fetchMock) {
defaultSetupMocks(fetchMock)
fetchMock.get(/\/user\/emails/, userData, {
delay: MOCK_DELAY,
overwriteRoutes: true,
})
}
@@ -6,10 +6,10 @@ import RegisterForm from '../../../../frontend/js/components/register-form'
describe('RegisterForm', function () {
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('should render the register form', async function () {
const setRegistrationSuccessStub = sinon.stub()
@@ -57,6 +57,6 @@ describe('RegisterForm', function () {
const registerButton = screen.getByRole('button', { name: /register/i })
fireEvent.change(registerInput, { target: { value: email } })
fireEvent.click(registerButton)
expect(registerMock.called()).to.be.true
expect(registerMock.callHistory.called()).to.be.true
})
})
@@ -5,10 +5,10 @@ import UserActivateRegister from '../../../../frontend/js/components/user-activa
describe('UserActivateRegister', function () {
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('should display the error message', async function () {
const email = 'abc@gmail.com'
@@ -23,7 +23,7 @@ describe('UserActivateRegister', function () {
fireEvent.change(registerInput, { target: { value: email } })
fireEvent.click(registerButton)
expect(registerMock.called()).to.be.true
expect(registerMock.callHistory.called()).to.be.true
await screen.findByText('Sorry, an error occured', { exact: false })
})
@@ -44,7 +44,7 @@ describe('UserActivateRegister', function () {
fireEvent.change(registerInput, { target: { value: email } })
fireEvent.click(registerButton)
expect(registerMock.called()).to.be.true
expect(registerMock.callHistory.called()).to.be.true
await screen.findByText(
"We've sent out welcome emails to the registered users."
)
@@ -78,7 +78,7 @@ describe('UserActivateRegister', function () {
fireEvent.change(registerInput, { target: { value: email } })
fireEvent.click(registerButton)
expect(registerMock.called()).to.be.true
expect(registerMock.callHistory.called()).to.be.true
await screen.findByText('abc@gmail.com')
await screen.findByText('def@gmail.com')
})
@@ -103,7 +103,7 @@ describe('UserActivateRegister', function () {
fireEvent.change(registerInput, { target: { value: email } })
fireEvent.click(registerButton)
expect(registerMock.called()).to.be.true
expect(registerMock.callHistory.called()).to.be.true
await screen.findByText('abc@')
await screen.findByText('def@')
})
@@ -133,7 +133,7 @@ describe('UserActivateRegister', function () {
fireEvent.change(registerInput, { target: { value: email } })
fireEvent.click(registerButton)
expect(registerMock.called()).to.be.true
expect(registerMock.callHistory.called()).to.be.true
await screen.findByText('abc@gmail.com')
await screen.findByText('def@')
})
+1 -1
View File
@@ -295,7 +295,7 @@
"esmock": "^2.6.7",
"events": "^3.3.0",
"fake-indexeddb": "^6.0.0",
"fetch-mock": "^9.10.2",
"fetch-mock": "^12.5.2",
"formik": "^2.2.9",
"fuse.js": "^3.0.0",
"glob": "^7.1.6",
+6
View File
@@ -98,3 +98,9 @@ globalThis.DOMParser = window.DOMParser
// Polyfill for IndexedDB
require('fake-indexeddb/auto')
const fetchMock = require('fetch-mock').default
fetchMock.spyGlobal()
fetchMock.config.fetch = global.fetch
fetchMock.config.Response = fetch.Response
@@ -26,7 +26,7 @@ describe('<ChatPane />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
const testMessages = [
@@ -45,7 +45,7 @@ describe('<ChatPane />', function () {
]
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
cleanUpContext()
stubMathJax()
@@ -73,7 +73,7 @@ describe('<ChatPane />', function () {
await screen.findByText('Try again')
// bring chat back up
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock.get(/messages/, [])
const reconnectButton = screen.getByRole('button', {
@@ -23,7 +23,7 @@ describe('ChatContext', function () {
const uuidValue = '00000000-0000-0000-0000-000000000000'
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
cleanUpContext()
stubMathJax()
@@ -43,14 +43,15 @@ describe('ChatContext', function () {
describe('socket connection', function () {
beforeEach(function () {
// Mock GET messages to return no messages
fetchMock.get('express:/project/:projectId/messages', [])
// FIXME?
// fetchMock.get('express:/project/:projectId/messages', [])
// Mock POST new message to return 200
fetchMock.post('express:/project/:projectId/messages', 200)
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('subscribes when mounted', function () {
@@ -106,22 +107,18 @@ describe('ChatContext', function () {
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.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',
},
],
{ overwriteRoutes: true }
)
},
])
// Mock message being received from another user
socket.emitToClient('new-chat-message', {
@@ -157,22 +154,18 @@ describe('ChatContext', function () {
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.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',
},
],
{ overwriteRoutes: true }
)
},
])
// Wait until initial messages have loaded
result.current.loadInitialMessages()
@@ -320,11 +313,13 @@ describe('ChatContext', function () {
// Calling a second time won't do anything
result.current.loadInitialMessages()
expect(fetchMock.calls()).to.have.lengthOf(1)
expect(
fetchMock.callHistory.calls('express:/project/:projectId/messages')
).to.have.lengthOf(1)
})
it('provides an error on failure', async function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock.get('express:/project/:projectId/messages', 500)
const { result, waitForNextUpdate } = renderChatContextHook({})
@@ -369,18 +364,14 @@ describe('ChatContext', function () {
// Resolve a full "page" of messages (50)
createMessages(50, user, new Date('2021-03-04T10:00:00').getTime())
)
.getOnce(
'express:/project/:projectId/messages',
[
{
id: 'msg_51',
content: 'message from second page',
user,
timestamp: new Date('2021-03-04T11:00:00').getTime(),
},
],
{ overwriteRoutes: false }
)
.getOnce('express:/project/:projectId/messages', [
{
id: 'msg_51',
content: 'message from second page',
user,
timestamp: new Date('2021-03-04T11:00:00').getTime(),
},
])
const { result, waitForNextUpdate } = renderChatContextHook({})
@@ -422,7 +413,9 @@ describe('ChatContext', function () {
result.current.loadMoreMessages()
expect(result.current.atEnd).to.be.true
expect(fetchMock.calls()).to.have.lengthOf(1)
expect(
fetchMock.callHistory.calls('express:/project/:projectId/messages')
).to.have.lengthOf(1)
})
it('handles socket messages while loading', async function () {
@@ -479,7 +472,7 @@ describe('ChatContext', function () {
})
it('provides an error on failures', async function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock.get('express:/project/:projectId/messages', 500)
const { result, waitForNextUpdate } = renderChatContextHook({})
@@ -515,10 +508,11 @@ describe('ChatContext', function () {
result.current.sendMessage('sent message')
const [, { body }] = fetchMock.lastCall(
'express:/project/:projectId/messages',
'POST'
)
const {
options: { body },
} = fetchMock.callHistory
.calls('express:/project/:projectId/messages', { method: 'POST' })
.at(-1)
expect(JSON.parse(body)).to.deep.include({ content: 'sent message' })
})
@@ -529,14 +523,14 @@ describe('ChatContext', function () {
expect(result.current.messages).to.be.empty
expect(
fetchMock.called('express:/project/:projectId/messages', {
fetchMock.callHistory.called('express:/project/:projectId/messages', {
method: 'post',
})
).to.be.false
})
it('provides an error on failure', async function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock
.get('express:/project/:projectId/messages', [])
.postOnce('express:/project/:projectId/messages', 500)
@@ -614,7 +608,7 @@ function createMessages(number, user, timestamp = Date.now()) {
* Get query param by key from the last fetchMock response
*/
function getLastFetchMockQueryParam(key) {
const url = fetchMock.lastUrl()
const { url } = fetchMock.callHistory.calls().at(-1)
const { searchParams } = new URL(url, 'https://www.overleaf.com')
return searchParams.get(key)
}
@@ -7,11 +7,11 @@ import { renderWithEditorContext } from '../../../helpers/render-with-context'
describe('<EditorCloneProjectModalWrapper />', function () {
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
after(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
const project = {
@@ -78,19 +78,23 @@ describe('<EditorCloneProjectModalWrapper />', function () {
fireEvent.click(submitButton)
expect(submitButton.disabled).to.be.true
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
const [url, options] = fetchMock.lastCall(
'express:/project/:projectId/clone'
await fetchMock.callHistory.flush(true)
expect(fetchMock.callHistory.done()).to.be.true
const { url, options } = fetchMock.callHistory
.calls('express:/project/:projectId/clone')
.at(-1)
expect(url).to.equal(
'https://www.test-overleaf.com/project/project-1/clone'
)
expect(url).to.equal('/project/project-1/clone')
expect(JSON.parse(options.body)).to.deep.equal({
projectName: 'A Cloned Project',
tags: [],
})
expect(openProject).to.be.calledOnce
await waitFor(() => {
expect(openProject).to.be.calledOnce
})
const errorMessage = screen.queryByText('Sorry, something went wrong')
expect(errorMessage).to.be.null
@@ -129,7 +133,7 @@ describe('<EditorCloneProjectModalWrapper />', function () {
fireEvent.click(button)
expect(fetchMock.done(matcher)).to.be.true
expect(fetchMock.callHistory.done(matcher)).to.be.true
expect(openProject).not.to.be.called
await screen.findByText('Sorry, something went wrong')
@@ -165,9 +169,9 @@ describe('<EditorCloneProjectModalWrapper />', function () {
expect(cancelButton.disabled).to.be.false
fireEvent.click(button)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(fetchMock.done(matcher)).to.be.true
expect(fetchMock.callHistory.done(matcher)).to.be.true
expect(openProject).not.to.be.called
await screen.findByText('There was an error!')
@@ -20,7 +20,7 @@ describe('<ActionsCopyProject />', function () {
afterEach(function () {
this.locationStub.restore()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct modal when clicked', async function () {
@@ -21,7 +21,7 @@ describe('<ActionsMenu />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu for non-anonymous users', async function () {
@@ -6,7 +6,7 @@ import { renderWithEditorContext } from '../../../helpers/render-with-context'
describe('<ActionsWordCount />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct modal when clicked after document is compiled', async function () {
@@ -51,11 +51,15 @@ describe('<ActionsWordCount />', function () {
// when loading, we don't render the "Word Count" as button yet
expect(screen.queryByRole('button', { name: 'Word Count' })).to.equal(null)
await waitFor(() => expect(fetchMock.called(compileEndpoint)).to.be.true)
await waitFor(
() => expect(fetchMock.callHistory.called(compileEndpoint)).to.be.true
)
const button = await screen.findByRole('button', { name: 'Word Count' })
button.click()
await waitFor(() => expect(fetchMock.called(wordcountEndpoint)).to.be.true)
await waitFor(
() => expect(fetchMock.callHistory.called(wordcountEndpoint)).to.be.true
)
})
})
@@ -6,7 +6,7 @@ import { renderWithEditorContext } from '../../../helpers/render-with-context'
describe('<DownloadMenu />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows download links with correct url', async function () {
@@ -14,7 +14,7 @@ describe('<HelpContactUs />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('open contact us modal when clicked', function () {
@@ -14,7 +14,7 @@ describe('<HelpMenu />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu if `showSupport` is `true`', function () {
@@ -6,7 +6,7 @@ import fetchMock from 'fetch-mock'
describe('<HelpShowHotkeys />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('open hotkeys modal when clicked', function () {
@@ -7,7 +7,7 @@ import { EditorProviders } from '../../../../helpers/editor-providers'
describe('<SettingsAutoCloseBrackets />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -7,7 +7,7 @@ import { EditorProviders } from '../../../../helpers/editor-providers'
describe('<SettingsAutoComplete />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -7,7 +7,7 @@ import { EditorProviders } from '../../../../helpers/editor-providers'
describe('<SettingsCompiler />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -30,7 +30,7 @@ describe('<SettingsDocument />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
window.metaAttributesCache.set('ol-ExposedSettings', originalSettings)
})
@@ -16,7 +16,7 @@ describe('<SettingsEditorTheme />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -7,7 +7,7 @@ import { EditorProviders } from '../../../../helpers/editor-providers'
describe('<SettingsFontFamily />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -9,7 +9,7 @@ describe('<SettingsFontSize />', function () {
const sizes = ['10', '11', '12', '13', '14', '16', '18', '20', '22', '24']
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -23,7 +23,7 @@ describe('<SettingsImageName />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -7,7 +7,7 @@ import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/e
describe('<SettingsKeybindings />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -7,7 +7,7 @@ import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/e
describe('<SettingsLineHeight />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -7,7 +7,7 @@ import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/e
describe('<SettingsMathPreview />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -32,7 +32,7 @@ describe('<SettingsOverallTheme />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -7,7 +7,7 @@ import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/e
describe('<SettingsPdfViewer />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -25,7 +25,7 @@ describe('<SettingsSpellCheckLanguage />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -7,7 +7,7 @@ import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/e
describe('<SettingsSyntaxValidation />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows correct menu', async function () {
@@ -22,7 +22,7 @@ describe('<LayoutDropdownButton />', function () {
afterEach(function () {
openStub.restore()
sendMBSpy.restore()
fetchMock.restore()
fetchMock.removeRoutes().clearHistory()
})
it('should mark current layout option as selected', async function () {
@@ -39,7 +39,7 @@ describe('<FileViewHeader/>', function () {
}
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
describe('header text', function () {
@@ -23,7 +23,7 @@ describe('<FileViewRefreshButton />', function () {
}
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
// eslint-disable-next-line mocha/no-skipped-tests
@@ -19,7 +19,7 @@ describe('<FileViewText/>', function () {
}
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('renders a text view', async function () {
@@ -32,7 +32,7 @@ describe('<FileView/>', function () {
}
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
describe('for a text file', function () {
@@ -31,7 +31,7 @@ describe('<UnlinkUserModal />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('displays the modal', async function () {
@@ -11,7 +11,7 @@ import { renderWithProjectListContext } from '../helpers/render-with-context'
describe('<LoadMore />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('renders on a project list longer than 40', async function () {
@@ -7,7 +7,7 @@ import getMeta from '@/utils/meta'
describe('<NewProjectButton />', function () {
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
describe('for every user (affiliated and non-affiliated)', function () {
@@ -20,7 +20,7 @@ describe('<ModalContentNewProjectForm />', function () {
afterEach(function () {
this.locationStub.restore()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('submits form', async function () {
@@ -49,7 +49,7 @@ describe('<ModalContentNewProjectForm />', function () {
fireEvent.click(createButton)
expect(newProjectMock.called()).to.be.true
expect(newProjectMock.callHistory.called()).to.be.true
await waitFor(() => {
sinon.assert.calledOnce(assignStub)
@@ -76,7 +76,7 @@ describe('<ModalContentNewProjectForm />', function () {
})
fireEvent.click(createButton)
expect(newProjectMock.called()).to.be.true
expect(newProjectMock.callHistory.called()).to.be.true
await waitFor(() => {
screen.getByText(errorMessage)
@@ -102,7 +102,7 @@ describe('<ModalContentNewProjectForm />', function () {
})
fireEvent.click(createButton)
expect(newProjectMock.called()).to.be.true
expect(newProjectMock.callHistory.called()).to.be.true
await waitFor(() => {
screen.getByText(errorMessage)
@@ -141,7 +141,7 @@ describe('<ModalContentNewProjectForm />', function () {
})
fireEvent.click(createButton)
expect(newProjectMock.called()).to.be.true
expect(newProjectMock.callHistory.called()).to.be.true
await waitFor(() => {
screen.getByText(errorMessage)
@@ -69,7 +69,7 @@ describe('<UserNotifications />', function () {
}
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
// at least one project is required to show some notifications
const projects = [{}] as Project[]
@@ -83,7 +83,7 @@ describe('<UserNotifications />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
describe('<Common>', function () {
@@ -93,7 +93,7 @@ describe('<UserNotifications />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('accepts project invite', async function () {
@@ -106,14 +106,14 @@ describe('<UserNotifications />', function () {
])
renderWithinProjectListProvider(Common)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
const deleteMock = fetchMock.delete(
`/notifications/${reconfiguredNotification._id}`,
200
)
const acceptMock = fetchMock.post(
`project/${notificationProjectInvite.messageOpts.projectId}/invite/token/${notificationProjectInvite.messageOpts.token}/accept`,
`/project/${notificationProjectInvite.messageOpts.projectId}/invite/token/${notificationProjectInvite.messageOpts.token}/accept`,
200
)
@@ -134,8 +134,9 @@ describe('<UserNotifications />', function () {
screen.getByRole('button', { name: /joining/i })
)
expect(acceptMock.called()).to.be.true
screen.getByText(/joined/i)
expect(acceptMock.callHistory.called()).to.be.true
await screen.findByText(/joined/i)
expect(screen.queryByRole('button', { name: /join project/i })).to.be.null
const openProject = screen.getByRole('button', { name: /open project/i })
@@ -146,7 +147,7 @@ describe('<UserNotifications />', function () {
const closeBtn = screen.getByRole('button', { name: /close/i })
fireEvent.click(closeBtn)
expect(deleteMock.called()).to.be.true
expect(deleteMock.callHistory.called()).to.be.true
expect(screen.queryByRole('alert')).to.be.null
})
@@ -160,9 +161,9 @@ describe('<UserNotifications />', function () {
])
renderWithinProjectListProvider(Common)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
fetchMock.post(
`project/${notificationProjectInvite.messageOpts.projectId}/invite/token/${notificationProjectInvite.messageOpts.token}/accept`,
`/project/${notificationProjectInvite.messageOpts.projectId}/invite/token/${notificationProjectInvite.messageOpts.token}/accept`,
500
)
@@ -179,7 +180,7 @@ describe('<UserNotifications />', function () {
screen.getByRole('button', { name: /joining/i })
)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
screen.getByRole('button', { name: /join project/i })
expect(screen.queryByRole('button', { name: /open project/i })).to.be.null
})
@@ -194,7 +195,7 @@ describe('<UserNotifications />', function () {
])
renderWithinProjectListProvider(Common)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
fetchMock.delete(`/notifications/${reconfiguredNotification._id}`, 200)
screen.getByRole('alert')
@@ -207,7 +208,7 @@ describe('<UserNotifications />', function () {
const closeBtn = screen.getByRole('button', { name: /close/i })
fireEvent.click(closeBtn)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
expect(screen.queryByRole('alert')).to.be.null
})
@@ -225,7 +226,7 @@ describe('<UserNotifications />', function () {
])
renderWithinProjectListProvider(Common)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
fetchMock.delete(`/notifications/${reconfiguredNotification._id}`, 200)
screen.getByRole('alert')
@@ -246,7 +247,7 @@ describe('<UserNotifications />', function () {
const closeBtn = screen.getByRole('button', { name: /close/i })
fireEvent.click(closeBtn)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
expect(screen.queryByRole('alert')).to.be.null
})
@@ -264,7 +265,7 @@ describe('<UserNotifications />', function () {
])
renderWithinProjectListProvider(Common)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
fetchMock.delete(`/notifications/${reconfiguredNotification._id}`, 200)
screen.getByRole('alert')
@@ -281,7 +282,7 @@ describe('<UserNotifications />', function () {
const closeBtn = screen.getByRole('button', { name: /close/i })
fireEvent.click(closeBtn)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
expect(screen.queryByRole('alert')).to.be.null
})
@@ -294,7 +295,7 @@ describe('<UserNotifications />', function () {
])
renderWithinProjectListProvider(Common)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
screen.getByRole('alert')
screen.getByText(/file limit/i)
@@ -323,7 +324,7 @@ describe('<UserNotifications />', function () {
])
renderWithinProjectListProvider(Common)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
fetchMock.delete(`/notifications/${reconfiguredNotification._id}`, 200)
screen.getByRole('alert')
@@ -337,7 +338,7 @@ describe('<UserNotifications />', function () {
const closeBtn = screen.getByRole('button', { name: /close/i })
fireEvent.click(closeBtn)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
expect(screen.queryByRole('alert')).to.be.null
})
@@ -355,7 +356,7 @@ describe('<UserNotifications />', function () {
])
renderWithinProjectListProvider(Common)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
fetchMock.delete(`/notifications/${reconfiguredNotification._id}`, 200)
screen.getByRole('alert')
@@ -371,7 +372,7 @@ describe('<UserNotifications />', function () {
const closeBtn = screen.getByRole('button', { name: /close/i })
fireEvent.click(closeBtn)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
expect(screen.queryByRole('alert')).to.be.null
})
@@ -385,7 +386,7 @@ describe('<UserNotifications />', function () {
])
renderWithinProjectListProvider(Common)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
fetchMock.delete(`/notifications/${reconfiguredNotification._id}`, 200)
screen.getByRole('alert')
@@ -394,7 +395,7 @@ describe('<UserNotifications />', function () {
const closeBtn = screen.getByRole('button', { name: /close/i })
fireEvent.click(closeBtn)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
expect(screen.queryByRole('alert')).to.be.null
})
@@ -414,7 +415,7 @@ describe('<UserNotifications />', function () {
])
renderWithinProjectListProvider(Common)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
fetchMock.delete(`/notifications/${notificationGroupInvite._id}`, 200)
screen.getByRole('alert')
screen.getByText('inviter@overleaf.com')
@@ -444,7 +445,7 @@ describe('<UserNotifications />', function () {
)
renderWithinProjectListProvider(Common)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
fetchMock.delete(
`/notifications/${notificationGroupInvite._id}`,
200
@@ -465,11 +466,11 @@ describe('<UserNotifications />', function () {
describe('<Institution>', function () {
beforeEach(function () {
Object.assign(getMeta('ol-ExposedSettings'), exposedSettings)
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows sso available', function () {
@@ -513,7 +514,7 @@ describe('<UserNotifications />', function () {
const closeBtn = screen.getByRole('button', { name: /close/i })
fireEvent.click(closeBtn)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
expect(screen.queryByRole('alert')).to.be.null
})
@@ -537,7 +538,7 @@ describe('<UserNotifications />', function () {
const closeBtn = screen.getByRole('button', { name: /close/i })
fireEvent.click(closeBtn)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
expect(screen.queryByRole('alert')).to.be.null
})
@@ -563,7 +564,7 @@ describe('<UserNotifications />', function () {
const closeBtn = screen.getByRole('button', { name: /close/i })
fireEvent.click(closeBtn)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
expect(screen.queryByRole('alert')).to.be.null
})
@@ -658,7 +659,7 @@ describe('<UserNotifications />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
this.clock.restore()
})
@@ -670,7 +671,7 @@ describe('<UserNotifications />', function () {
window.metaAttributesCache.set('ol-userEmails', userEmails)
renderWithinProjectListProvider(ConfirmEmail)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
fetchMock.post('/user/emails/resend_confirmation', 200)
const email = userEmails[0].email
@@ -693,7 +694,7 @@ describe('<UserNotifications />', function () {
screen.getByRole('button', { name: /resend/i })
)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
expect(screen.queryByRole('alert')).to.be.null
})
}
@@ -712,7 +713,7 @@ describe('<UserNotifications />', function () {
window.metaAttributesCache.set('ol-userEmails', [untrustedUserData])
renderWithinProjectListProvider(ConfirmEmail)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
fetchMock.post('/user/emails/resend_confirmation', 200)
const email = untrustedUserData.email
@@ -730,7 +731,7 @@ describe('<UserNotifications />', function () {
screen.getByRole('button', { name: /resend/i })
)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
expect(screen.queryByRole('alert')).to.be.null
})
@@ -738,7 +739,7 @@ describe('<UserNotifications />', function () {
window.metaAttributesCache.set('ol-userEmails', [unconfirmedUserData])
renderWithinProjectListProvider(ConfirmEmail)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
fetchMock.post('/user/emails/resend_confirmation', 500)
const resendButtons = screen.getAllByRole('button', { name: /resend/i })
@@ -752,7 +753,7 @@ describe('<UserNotifications />', function () {
)
)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
screen.getByText(/something went wrong/i)
})
@@ -764,7 +765,7 @@ describe('<UserNotifications />', function () {
window.metaAttributesCache.set('ol-usersBestSubscription', subscription)
renderWithinProjectListProvider(ConfirmEmail)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
const alert = screen.getByRole('alert')
const email = unconfirmedCommonsUserData.email
@@ -785,7 +786,7 @@ describe('<UserNotifications />', function () {
window.metaAttributesCache.set('ol-usersBestSubscription', subscription)
renderWithinProjectListProvider(ConfirmEmail)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
const alert = screen.getByRole('alert')
const email = unconfirmedCommonsUserData.email
@@ -818,12 +819,12 @@ describe('<UserNotifications />', function () {
reload: sinon.stub(),
setHash: sinon.stub(),
})
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
afterEach(function () {
this.locationStub.restore()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows reconfirm message with SSO disabled', async function () {
@@ -860,12 +861,12 @@ describe('<UserNotifications />', function () {
.be.null
expect(screen.queryByRole('link', { name: /remove it/i })).to.be.null
expect(screen.queryByRole('link', { name: /learn more/i })).to.be.null
expect(sendReconfirmationMock.called()).to.be.true
expect(sendReconfirmationMock.callHistory.called()).to.be.true
fireEvent.click(
screen.getByRole('button', { name: /resend confirmation email/i })
)
await waitForElementToBeRemoved(() => screen.getByText('Sending…'))
expect(sendReconfirmationMock.calls()).to.have.lengthOf(2)
expect(sendReconfirmationMock.callHistory.calls()).to.have.lengthOf(2)
})
it('shows reconfirm message with SSO enabled', async function () {
@@ -905,7 +906,7 @@ describe('<UserNotifications />', function () {
describe('<GroupsAndEnterpriseBanner />', function () {
beforeEach(function () {
localStorage.clear()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
// at least one project is required to show some notifications
const projects = [{}] as Project[]
@@ -924,14 +925,14 @@ describe('<UserNotifications />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('does not show the banner for users that are in group or are affiliated', async function () {
window.metaAttributesCache.set('ol-showGroupsAndEnterpriseBanner', false)
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.be.null
})
@@ -941,7 +942,7 @@ describe('<UserNotifications />', function () {
localStorage.setItem('has_dismissed_groups_and_enterprise_banner', true)
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.not.be
.null
@@ -957,7 +958,7 @@ describe('<UserNotifications />', function () {
)
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.not.be
.null
@@ -973,7 +974,7 @@ describe('<UserNotifications />', function () {
)
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.be.null
})
@@ -981,7 +982,7 @@ describe('<UserNotifications />', function () {
describe('users that are not in group and are not affiliated', function () {
beforeEach(function () {
localStorage.clear()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
// at least one project is required to show some notifications
const projects = [{}] as Project[]
@@ -997,7 +998,7 @@ describe('<UserNotifications />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
after(function () {
@@ -1011,7 +1012,7 @@ describe('<UserNotifications />', function () {
)
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
screen.getByText(
'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.'
@@ -1028,7 +1029,7 @@ describe('<UserNotifications />', function () {
)
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
screen.getByText(
'Why do Fortune 500 companies and top research institutions trust Overleaf to streamline their collaboration? Get in touch to learn more.'
@@ -79,7 +79,7 @@ describe('<ProjectListRoot />', function () {
afterEach(function () {
sendMBSpy.restore()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
this.locationStub.restore()
})
@@ -88,11 +88,11 @@ describe('<ProjectListRoot />', function () {
renderWithProjectListContext(<ProjectListRoot />, {
projects: [],
})
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
})
it('the welcome page is displayed', async function () {
screen.getByRole('heading', { name: 'Welcome to Overleaf' })
await screen.findByRole('heading', { name: 'Welcome to Overleaf' })
})
it('the email confirmation alert is not displayed', async function () {
@@ -110,7 +110,7 @@ describe('<ProjectListRoot />', function () {
projects: fullList,
})
this.unmount = unmount
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
await screen.findByRole('table')
})
@@ -172,13 +172,17 @@ describe('<ProjectListRoot />', function () {
await waitFor(
() =>
expect(
archiveProjectMock.called(`/project/${project1Id}/archive`)
archiveProjectMock.callHistory.called(
`/project/${project1Id}/archive`
)
).to.be.true
)
await waitFor(
() =>
expect(
archiveProjectMock.called(`/project/${project2Id}/archive`)
archiveProjectMock.callHistory.called(
`/project/${project2Id}/archive`
)
).to.be.true
)
})
@@ -203,13 +207,19 @@ describe('<ProjectListRoot />', function () {
await waitFor(
() =>
expect(trashProjectMock.called(`/project/${project1Id}/trash`)).to
.be.true
expect(
trashProjectMock.callHistory.called(
`/project/${project1Id}/trash`
)
).to.be.true
)
await waitFor(
() =>
expect(trashProjectMock.called(`/project/${project2Id}/trash`)).to
.be.true
expect(
trashProjectMock.callHistory.called(
`/project/${project2Id}/trash`
)
).to.be.true
)
})
@@ -281,8 +291,8 @@ describe('<ProjectListRoot />', function () {
})
fireEvent.click(unarchiveButton)
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
await fetchMock.callHistory.flush(true)
expect(fetchMock.callHistory.done()).to.be.true
await screen.findByText('No projects')
})
@@ -296,8 +306,8 @@ describe('<ProjectListRoot />', function () {
archivedProjects.length - 1
)
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
await fetchMock.callHistory.flush(true)
expect(fetchMock.callHistory.done()).to.be.true
expect(screen.queryByText('No projects')).to.be.null
})
@@ -348,8 +358,8 @@ describe('<ProjectListRoot />', function () {
within(actionsToolbar).getByText<HTMLButtonElement>('Restore')
fireEvent.click(untrashButton)
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
await fetchMock.callHistory.flush(true)
expect(fetchMock.callHistory.done()).to.be.true
await screen.findByText('No projects')
})
@@ -361,8 +371,8 @@ describe('<ProjectListRoot />', function () {
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
expect(allCheckboxesChecked.length).to.equal(trashedList.length - 1)
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
await fetchMock.callHistory.flush(true)
expect(fetchMock.callHistory.done()).to.be.true
expect(screen.queryByText('No projects')).to.be.null
})
@@ -386,26 +396,28 @@ describe('<ProjectListRoot />', function () {
fireEvent.click(confirmButton)
expect(confirmButton.disabled).to.be.true
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
await fetchMock.callHistory.flush(true)
expect(fetchMock.callHistory.done()).to.be.true
const calls = fetchMock.calls().map(([url]) => url)
const calls = fetchMock.callHistory.calls().map(({ url }) => url)
trashedList.forEach(project => {
expect(calls).to.contain(`/project/${project.id}/archive`)
expect(calls).to.contain(
`https://www.test-overleaf.com/project/${project.id}/archive`
)
})
})
it('removes only selected projects from view when leaving', async function () {
// rerender content with different projects
this.unmount()
fetchMock.restore()
fetchMock.removeRoutes().clearHistory()
renderWithProjectListContext(<ProjectListRoot />, {
projects: leavableList,
})
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
await screen.findByRole('table')
expect(leavableList.length).to.be.greaterThan(0)
@@ -446,25 +458,27 @@ describe('<ProjectListRoot />', function () {
fireEvent.click(confirmButton)
expect(confirmButton.disabled).to.be.true
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
await fetchMock.callHistory.flush(true)
expect(fetchMock.callHistory.done()).to.be.true
const calls = fetchMock.calls().map(([url]) => url)
const calls = fetchMock.callHistory.calls().map(({ url }) => url)
leavableList.forEach(project => {
expect(calls).to.contain(`/project/${project.id}/leave`)
expect(calls).to.contain(
`https://www.test-overleaf.com/project/${project.id}/leave`
)
})
})
it('removes only selected projects from view when deleting', async function () {
// rerender content with different projects
this.unmount()
fetchMock.restore()
fetchMock.removeRoutes().clearHistory()
renderWithProjectListContext(<ProjectListRoot />, {
projects: deletableList,
})
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
await screen.findByRole('table')
expect(deletableList.length).to.be.greaterThan(0)
@@ -505,19 +519,21 @@ describe('<ProjectListRoot />', function () {
fireEvent.click(confirmButton)
expect(confirmButton.disabled).to.be.true
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
await fetchMock.callHistory.flush(true)
expect(fetchMock.callHistory.done()).to.be.true
const calls = fetchMock.calls().map(([url]) => url)
const calls = fetchMock.callHistory.calls().map(({ url }) => url)
deletableList.forEach(project => {
expect(calls).to.contain(`/project/${project.id}`)
expect(calls).to.contain(
`https://www.test-overleaf.com/project/${project.id}`
)
})
})
it('removes only selected projects from view when deleting and leaving', async function () {
// rerender content with different projects
this.unmount()
fetchMock.restore()
fetchMock.removeRoutes().clearHistory()
const deletableAndLeavableList = [...deletableList, ...leavableList]
@@ -525,7 +541,7 @@ describe('<ProjectListRoot />', function () {
projects: deletableAndLeavableList,
})
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
await screen.findByRole('table')
expect(deletableList.length).to.be.greaterThan(0)
@@ -573,14 +589,14 @@ describe('<ProjectListRoot />', function () {
fireEvent.click(confirmButton)
expect(confirmButton.disabled).to.be.true
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
await fetchMock.callHistory.flush(true)
expect(fetchMock.callHistory.done()).to.be.true
const calls = fetchMock.calls().map(([url]) => url)
const calls = fetchMock.callHistory.calls().map(({ url }) => url)
deletableAndLeavableList.forEach(project => {
expect(calls).to.contain.oneOf([
`/project/${project.id}`,
`/project/${project.id}/leave`,
`https://www.test-overleaf.com/project/${project.id}`,
`https://www.test-overleaf.com/project/${project.id}/leave`,
])
})
})
@@ -589,7 +605,7 @@ describe('<ProjectListRoot />', function () {
describe('tags', function () {
it('does not show archived or trashed project', async function () {
this.unmount()
fetchMock.restore()
fetchMock.removeRoutes().clearHistory()
window.metaAttributesCache.set('ol-tags', [
{
_id: this.tagId,
@@ -642,7 +658,7 @@ describe('<ProjectListRoot />', function () {
)
await waitFor(() => {
expect(
trashProjectMock.called(
trashProjectMock.callHistory.called(
`/project/${projectsData[index].id}/trash`
)
).to.be.true
@@ -702,11 +718,13 @@ describe('<ProjectListRoot />', function () {
})
fireEvent.click(createButton)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(fetchMock.called('/tag', { name: this.newTagName })).to.be.true
expect(
fetchMock.called(`/tag/${this.newTagId}/projects`, {
fetchMock.callHistory.called('/tag', { name: this.newTagName })
).to.be.true
expect(
fetchMock.callHistory.called(`/tag/${this.newTagId}/projects`, {
body: {
projectIds: [projectsData[0].id, projectsData[1].id],
},
@@ -734,10 +752,10 @@ describe('<ProjectListRoot />', function () {
)
fireEvent.click(tagButton)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(
deleteProjectsFromTagMock.called(
deleteProjectsFromTagMock.callHistory.called(
`/tag/${this.tagId}/projects/remove`,
{
body: {
@@ -770,14 +788,17 @@ describe('<ProjectListRoot />', function () {
)
fireEvent.click(tagButton)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(
addProjectsToTagMock.called(`/tag/${this.tagId}/projects`, {
body: {
projectIds: [projectsData[2].id],
},
})
addProjectsToTagMock.callHistory.called(
`/tag/${this.tagId}/projects`,
{
body: {
projectIds: [projectsData[2].id],
},
}
)
).to.be.true
screen.getByRole('button', { name: `${this.tagName} (3)` })
})
@@ -919,10 +940,12 @@ describe('<ProjectListRoot />', function () {
expect(confirmButton.disabled).to.be.false
fireEvent.click(confirmButton)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(
renameProjectMock.called(`/project/${projectsData[0].id}/rename`)
renameProjectMock.callHistory.called(
`/project/${projectsData[0].id}/rename`
)
).to.be.true
const table = await screen.findByRole('table')
@@ -982,10 +1005,12 @@ describe('<ProjectListRoot />', function () {
) as HTMLElement
fireEvent.click(copyConfirmButton)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(
cloneProjectMock.called(`/project/${projectsData[1].id}/clone`)
cloneProjectMock.callHistory.called(
`/project/${projectsData[1].id}/clone`
)
).to.be.true
expect(sendMBSpy).to.have.been.calledTwice
@@ -1149,8 +1174,8 @@ describe('<ProjectListRoot />', function () {
) as HTMLElement
fireEvent.click(copyConfirmButton)
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
await fetchMock.callHistory.flush(true)
expect(fetchMock.callHistory.done()).to.be.true
expect(sendMBSpy).to.have.been.calledTwice
expect(sendMBSpy).to.have.been.calledWith('loads_v2_dash')
@@ -12,11 +12,11 @@ describe('Project list search form', function () {
beforeEach(function () {
sendMBSpy = sinon.spy(eventTracking, 'sendMB')
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
sendMBSpy.restore()
})
@@ -14,11 +14,11 @@ describe('Add affiliation widget', function () {
}
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('renders the component', async function () {
@@ -27,8 +27,8 @@ describe('Add affiliation widget', function () {
renderWithProjectListContext(<AddAffiliation />)
await fetchMock.flush(true)
await waitFor(() => expect(fetchMock.called('/api/project')))
await fetchMock.callHistory.flush(true)
await waitFor(() => expect(fetchMock.callHistory.called('/api/project')))
screen.getByText(/are you affiliated with an institution/i)
const addAffiliationLink = screen.getByRole('button', {
@@ -43,8 +43,8 @@ describe('Add affiliation widget', function () {
renderWithProjectListContext(<AddAffiliation />)
await fetchMock.flush(true)
await waitFor(() => expect(fetchMock.called('/api/project')))
await fetchMock.callHistory.flush(true)
await waitFor(() => expect(fetchMock.callHistory.called('/api/project')))
validateNonExistence()
})
@@ -57,8 +57,8 @@ describe('Add affiliation widget', function () {
projects: [],
})
await fetchMock.flush(true)
await waitFor(() => expect(fetchMock.called('/api/project')))
await fetchMock.callHistory.flush(true)
await waitFor(() => expect(fetchMock.callHistory.called('/api/project')))
validateNonExistence()
})
@@ -69,8 +69,8 @@ describe('Add affiliation widget', function () {
renderWithProjectListContext(<AddAffiliation />)
await fetchMock.flush(true)
await waitFor(() => expect(fetchMock.called('/api/project')))
await fetchMock.callHistory.flush(true)
await waitFor(() => expect(fetchMock.callHistory.called('/api/project')))
validateNonExistence()
})
@@ -29,16 +29,16 @@ describe('<TagsList />', function () {
})
fetchMock.post('express:/tag/:tagId/projects', 200)
fetchMock.post('express:/tag/:tagId/edit', 200)
fetchMock.delete('express:/tag/:tagId', 200)
fetchMock.delete('express:/tag/:tagId', 200, { name: 'delete tag' })
renderWithProjectListContext(<TagsList />)
await fetchMock.flush(true)
await waitFor(() => expect(fetchMock.called('/api/project')))
await fetchMock.callHistory.flush(true)
await waitFor(() => expect(fetchMock.callHistory.called('/api/project')))
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('displays the tags list', function () {
@@ -146,7 +146,9 @@ describe('<TagsList />', function () {
await fireEvent.click(createButton)
await waitFor(() => expect(fetchMock.called(`/tag`)).to.be.true)
await waitFor(
() => expect(fetchMock.callHistory.called(`/tag`)).to.be.true
)
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
@@ -237,7 +239,9 @@ describe('<TagsList />', function () {
await fireEvent.click(saveButton)
await waitFor(() => expect(fetchMock.called(`/tag/abc123def456/rename`)))
await waitFor(() =>
expect(fetchMock.callHistory.called(`/tag/abc123def456/rename`))
)
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
@@ -278,7 +282,9 @@ describe('<TagsList />', function () {
const deleteButton = within(modal).getByRole('button', { name: 'Delete' })
await fireEvent.click(deleteButton)
await waitFor(() => expect(fetchMock.called(`/tag/bcd234efg567`)))
await waitFor(() =>
expect(fetchMock.callHistory.called(`/tag/bcd234efg567`))
)
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
expect(
@@ -289,13 +295,15 @@ describe('<TagsList />', function () {
})
it('a failed request displays an error message', async function () {
fetchMock.delete('express:/tag/:tagId', 500, { overwriteRoutes: true })
fetchMock.modifyRoute('delete tag', { response: { status: 500 } })
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
const deleteButton = within(modal).getByRole('button', { name: 'Delete' })
await fireEvent.click(deleteButton)
await waitFor(() => expect(fetchMock.called(`/tag/bcd234efg567`)))
await waitFor(() =>
expect(fetchMock.callHistory.called(`/tag/bcd234efg567`))
)
within(modal).getByText('Sorry, something went wrong')
})
@@ -5,12 +5,12 @@ import SystemMessages from '@/shared/components/system-messages'
describe('<SystemMessages />', function () {
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
localStorage.clear()
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
localStorage.clear()
})
@@ -22,7 +22,7 @@ describe('<SystemMessages />', function () {
fetchMock.get(/\/system\/messages/, [data])
render(<SystemMessages />)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
screen.getByText(data.content)
expect(screen.queryByRole('button', { name: /close/i })).to.be.null
@@ -36,7 +36,7 @@ describe('<SystemMessages />', function () {
fetchMock.get(/\/system\/messages/, [data])
render(<SystemMessages />)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
screen.getByText(data.content)
const closeBtn = screen.getByRole('button', { name: /close/i })
@@ -60,7 +60,7 @@ describe('<SystemMessages />', function () {
window.metaAttributesCache.set('ol-currentUrl', currentUrl)
render(<SystemMessages />)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
const link = screen.getByRole('link', { name: /click here/i })
expect(link.getAttribute('href')).to.equal(`${data.url}${currentUrl}`)
@@ -67,8 +67,11 @@ describe('<ArchiveProjectButton />', function () {
await waitFor(
() =>
expect(archiveProjectMock.called(`/project/${project.id}/archive`)).to
.be.true
expect(
archiveProjectMock.callHistory.called(
`/project/${project.id}/archive`
)
).to.be.true
)
})
})
@@ -28,7 +28,7 @@ describe('<CompileAndDownloadProjectPDFButton />', function () {
afterEach(function () {
locationStub.restore()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
sendMBSpy.restore()
})
@@ -66,8 +66,11 @@ describe('<CopyProjectButton />', function () {
await waitFor(
() =>
expect(copyProjectMock.called(`/project/${copyableProject.id}/clone`))
.to.be.true
expect(
copyProjectMock.callHistory.called(
`/project/${copyableProject.id}/clone`
)
).to.be.true
)
})
})
@@ -69,7 +69,8 @@ describe('<DeleteProjectButton />', function () {
await waitFor(
() =>
expect(deleteProjectMock.called(`/project/${project.id}`)).to.be.true
expect(deleteProjectMock.callHistory.called(`/project/${project.id}`))
.to.be.true
)
})
})
@@ -75,8 +75,9 @@ describe('<LeaveProjectButtton />', function () {
await waitFor(
() =>
expect(leaveProjectMock.called(`/project/${project.id}/leave`)).to.be
.true
expect(
leaveProjectMock.callHistory.called(`/project/${project.id}/leave`)
).to.be.true
)
})
})
@@ -63,8 +63,9 @@ describe('<RenameProjectButton />', function () {
await waitFor(
() =>
expect(renameProjectMock.called(`/project/${project.id}/rename`)).to.be
.true
expect(
renameProjectMock.callHistory.called(`/project/${project.id}/rename`)
).to.be.true
)
})
})
@@ -57,8 +57,9 @@ describe('<TrashProjectButton />', function () {
await waitFor(
() =>
expect(trashProjectMock.called(`/project/${project.id}/trash`)).to.be
.true
expect(
trashProjectMock.callHistory.called(`/project/${project.id}/trash`)
).to.be.true
)
})
})
@@ -57,8 +57,11 @@ describe('<UnarchiveProjectButton />', function () {
await waitFor(
() =>
expect(unarchiveProjectMock.called(`/project/${project.id}/archive`)).to
.be.true
expect(
unarchiveProjectMock.callHistory.called(
`/project/${project.id}/archive`
)
).to.be.true
)
})
})
@@ -49,8 +49,9 @@ describe('<UntrashProjectButton />', function () {
await waitFor(
() =>
expect(untrashProjectMock.called(`/project/${project.id}/trash`)).to.be
.true
expect(
untrashProjectMock.callHistory.called(`/project/${project.id}/trash`)
).to.be.true
)
})
})
@@ -60,9 +60,12 @@ describe('<InlineTags />', function () {
await fireEvent.click(removeButton)
await waitFor(() =>
expect(
fetchMock.called(`/tag/789fff789fff/project/${copyableProject.id}`, {
method: 'DELETE',
})
fetchMock.callHistory.called(
`/tag/789fff789fff/project/${copyableProject.id}`,
{
method: 'DELETE',
}
)
)
)
expect(screen.queryByText('My Test Tag')).to.not.exist
@@ -11,11 +11,11 @@ describe('<ProjectListTable />', function () {
beforeEach(function () {
window.metaAttributesCache.set('ol-tags', [])
window.metaAttributesCache.set('ol-user_id', userId)
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('renders the table', function () {
@@ -65,7 +65,7 @@ describe('<ProjectListTable />', function () {
this.timeout(10000)
renderWithProjectListContext(<ProjectListTable />)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
const rows = screen.getAllByRole('row')
rows.shift() // remove first row since it's the header
@@ -138,7 +138,7 @@ describe('<ProjectListTable />', function () {
it('selects all projects when header checkbox checked', async function () {
renderWithProjectListContext(<ProjectListTable />)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
const checkbox = screen.getByLabelText('Select all projects')
fireEvent.click(checkbox)
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
@@ -149,7 +149,7 @@ describe('<ProjectListTable />', function () {
it('unselects all projects when select all checkbox uchecked', async function () {
renderWithProjectListContext(<ProjectListTable />)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
const checkbox = screen.getByLabelText('Select all projects')
fireEvent.click(checkbox)
fireEvent.click(checkbox)
@@ -160,7 +160,7 @@ describe('<ProjectListTable />', function () {
it('unselects select all projects checkbox when one project is unchecked', async function () {
renderWithProjectListContext(<ProjectListTable />)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
const checkbox = screen.getByLabelText('Select all projects')
fireEvent.click(checkbox)
let allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
@@ -173,7 +173,7 @@ describe('<ProjectListTable />', function () {
it('only checks the checked project', async function () {
renderWithProjectListContext(<ProjectListTable />)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
const checkbox = screen.getByLabelText(`Select ${currentProjects[0].name}`)
fireEvent.click(checkbox)
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
@@ -65,7 +65,7 @@ describe('<ProjectTools />', function () {
afterEach(function () {
window.metaAttributesCache.clear()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('does not show the Rename option for a project owned by a different user', function () {
@@ -58,7 +58,9 @@ describe('<RenameProjectModal />', function () {
await waitFor(
() =>
expect(
renameProjectMock.called(`/project/${currentProjects[0].id}/rename`)
renameProjectMock.callHistory.called(
`/project/${currentProjects[0].id}/rename`
)
).to.be.true
)
})
@@ -88,7 +90,7 @@ describe('<RenameProjectModal />', function () {
const submitButton = within(modal).getByText('Rename') as HTMLButtonElement
fireEvent.click(submitButton)
await waitFor(() => expect(postRenameMock.called()).to.be.true)
await waitFor(() => expect(postRenameMock.callHistory.called()).to.be.true)
screen.getByText('Something went wrong. Please try again.')
})
@@ -58,5 +58,5 @@ export function renderWithProjectListContext(
}
export function resetProjectListContextFetch() {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
}
@@ -29,7 +29,7 @@ describe('<AccountInfoSection />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('submits all inputs', async function () {
@@ -50,14 +50,14 @@ describe('<AccountInfoSection />', function () {
name: 'Update',
})
)
expect(updateMock.called()).to.be.true
expect(JSON.parse(updateMock.lastCall()![1]!.body as string)).to.deep.equal(
{
email: 'john@watson.co.uk',
first_name: 'John',
last_name: 'Watson',
}
)
expect(updateMock.callHistory.called()).to.be.true
expect(
JSON.parse(updateMock.callHistory.calls().at(-1)?.options.body as string)
).to.deep.equal({
email: 'john@watson.co.uk',
first_name: 'John',
last_name: 'Watson',
})
})
it('disables button on invalid email', async function () {
@@ -74,7 +74,7 @@ describe('<AccountInfoSection />', function () {
expect(button.disabled).to.be.true
fireEvent.click(button)
expect(updateMock.called()).to.be.false
expect(updateMock.callHistory.called()).to.be.false
})
it('shows inflight state and success message', async function () {
@@ -156,12 +156,12 @@ describe('<AccountInfoSection />', function () {
name: 'Update',
})
)
expect(JSON.parse(updateMock.lastCall()![1]!.body as string)).to.deep.equal(
{
first_name: 'Sherlock',
last_name: 'Holmes',
}
)
expect(
JSON.parse(updateMock.callHistory.calls().at(-1)?.options.body as string)
).to.deep.equal({
first_name: 'Sherlock',
last_name: 'Holmes',
})
})
it('disables email input', async function () {
@@ -187,12 +187,12 @@ describe('<AccountInfoSection />', function () {
name: 'Update',
})
)
expect(JSON.parse(updateMock.lastCall()![1]!.body as string)).to.deep.equal(
{
first_name: 'Sherlock',
last_name: 'Holmes',
}
)
expect(
JSON.parse(updateMock.callHistory.calls().at(-1)?.options.body as string)
).to.deep.equal({
first_name: 'Sherlock',
last_name: 'Holmes',
})
})
it('disables names input', async function () {
@@ -215,10 +215,10 @@ describe('<AccountInfoSection />', function () {
name: 'Update',
})
)
expect(JSON.parse(updateMock.lastCall()![1]!.body as string)).to.deep.equal(
{
email: 'sherlock@holmes.co.uk',
}
)
expect(
JSON.parse(updateMock.callHistory.calls().at(-1)?.options.body as string)
).to.deep.equal({
email: 'sherlock@holmes.co.uk',
})
})
})
@@ -19,7 +19,7 @@ describe('<AddEmailInput/>', function () {
beforeEach(function () {
clearDomainCache()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
describe('on initial render', function () {
@@ -69,7 +69,7 @@ describe('<AddEmailInput/>', function () {
})
it('should not make any request for institution domains', function () {
expect(fetchMock.called()).to.be.false
expect(fetchMock.callHistory.called()).to.be.false
})
it('should submit on Enter if email looks valid', async function () {
@@ -118,11 +118,18 @@ describe('<AddEmailInput/>', function () {
})
describe('when there is a domain match', function () {
beforeEach(function () {
beforeEach(async function () {
fetchMock.get('express:/institutions/domains', testInstitutionData)
fireEvent.change(screen.getByTestId('affiliations-email'), {
target: { value: 'user@d' },
})
// Wait for component to process the change and update the shadow input
await waitFor(() => {
const shadowInput = screen.getByTestId(
'affiliations-email-shadow'
) as HTMLInputElement
expect(shadowInput.value).to.equal('user@domain.edu')
})
})
it('should render the text being typed along with the suggestion', async function () {
@@ -146,7 +153,7 @@ describe('<AddEmailInput/>', function () {
fireEvent.change(screen.getByTestId('affiliations-email'), {
target: { value: 'user@domain.edu' },
})
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(
onChangeStub.calledWith(
'user@domain.edu',
@@ -231,7 +238,7 @@ describe('<AddEmailInput/>', function () {
})
it('should cache the result and skip subsequent requests', async function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
// clear input
fireEvent.change(screen.getByTestId('affiliations-email'), {
@@ -242,7 +249,7 @@ describe('<AddEmailInput/>', function () {
target: { value: 'user@d' },
})
expect(fetchMock.called()).to.be.false
expect(fetchMock.callHistory.called()).to.be.false
expect(onChangeStub.calledWith('user@d')).to.equal(true)
await waitFor(() => {
const shadowInput = screen.getByTestId(
@@ -258,7 +265,7 @@ describe('<AddEmailInput/>', function () {
afterEach(function () {
clearDomainCache()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('should not render the suggestion with blocked domain', async function () {
@@ -269,7 +276,7 @@ describe('<AddEmailInput/>', function () {
fireEvent.change(screen.getByTestId('affiliations-email'), {
target: { value: `user@${blockedDomain.split('.')[0]}` },
})
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(screen.queryByText(`user@${blockedDomain}`)).to.be.null
})
@@ -286,7 +293,7 @@ describe('<AddEmailInput/>', function () {
value: `user@subdomain.${blockedDomain.split('.')[0]}`,
},
})
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(screen.queryByText(`user@subdomain.${blockedDomain}`)).to.be.null
})
})
@@ -307,7 +314,7 @@ describe('<AddEmailInput/>', function () {
// make sure the next suggestions are delayed
clearDomainCache()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock.get('express:/institutions/domains', 200, { delay: 1000 })
})
@@ -354,7 +361,7 @@ describe('<AddEmailInput/>', function () {
})
// subsequent requests fail
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock.get('express:/institutions/domains', 500)
})
@@ -373,7 +380,7 @@ describe('<AddEmailInput/>', function () {
expect(shadowInput.value).to.equal('')
})
expect(fetchMock.called()).to.be.true // ensures `domainCache` hasn't been hit
expect(fetchMock.callHistory.called()).to.be.true // ensures `domainCache` hasn't been hit
})
})
@@ -384,7 +391,7 @@ describe('<AddEmailInput/>', function () {
fireEvent.change(screen.getByTestId('affiliations-email'), {
target: { value: 'user@other' },
})
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
const shadowInput = screen.getByTestId(
'affiliations-email-shadow'
) as HTMLInputElement
@@ -37,7 +37,7 @@ describe('<EmailsRow/>', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
describe('with unaffiliated email data', function () {
@@ -86,9 +86,9 @@ describe('<EmailsRow/>', function () {
describe('when the email is not yet linked to the institution', function () {
beforeEach(async function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock.get(/\/user\/emails/, [affiliatedEmail, unconfirmedUserData])
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
})
it('prompts the user to link to their institutional account', function () {
@@ -103,9 +103,9 @@ describe('<EmailsRow/>', function () {
describe('when the email is already linked to the institution', function () {
beforeEach(async function () {
affiliatedEmail.samlProviderId = '1'
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock.get(/\/user\/emails/, [affiliatedEmail, unconfirmedUserData])
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
})
it('prompts the user to login using their institutional account', function () {
@@ -37,11 +37,11 @@ describe('email actions - make primary', function () {
Object.assign(getMeta('ol-ExposedSettings'), {
hasAffiliationsFeature: true,
})
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
describe('disabled `make primary` button', function () {
@@ -236,11 +236,11 @@ describe('email actions - delete', function () {
Object.assign(getMeta('ol-ExposedSettings'), {
hasAffiliationsFeature: true,
})
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows loader when deleting and removes the row', async function () {
@@ -56,7 +56,7 @@ const institutionDomainData = [
] as const
function resetFetchMock() {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock.get('express:/institutions/domains', [])
}
@@ -80,7 +80,7 @@ describe('<EmailsSection />', function () {
hasSamlFeature: true,
samlInitPath: 'saml/init',
})
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
afterEach(function () {
@@ -97,7 +97,7 @@ describe('<EmailsSection />', function () {
it('renders input', async function () {
fetchMock.get('/user/emails?ensureAffiliation=true', [])
render(<EmailsSection />)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
const button = await screen.findByRole<HTMLButtonElement>('button', {
name: /add another email/i,
@@ -112,7 +112,7 @@ describe('<EmailsSection />', function () {
fetchMock.get(`/institutions/domains?hostname=email.com&limit=1`, 200)
fetchMock.get(`/institutions/domains?hostname=email&limit=1`, 200)
render(<EmailsSection />)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
const button = await screen.findByRole<HTMLButtonElement>('button', {
name: /add another email/i,
@@ -190,7 +190,7 @@ describe('<EmailsSection />', function () {
{ name: /add another email/i }
)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
resetFetchMock()
fetchMock
.get('/user/emails?ensureAffiliation=true', [userEmailData])
@@ -233,7 +233,7 @@ describe('<EmailsSection />', function () {
{ name: /add another email/i }
)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
resetFetchMock()
fetchMock
.get('/user/emails?ensureAffiliation=true', [])
@@ -271,8 +271,8 @@ describe('<EmailsSection />', function () {
name: /add another email/i,
})
await fetchMock.flush(true)
fetchMock.reset()
await fetchMock.callHistory.flush(true)
fetchMock.removeRoutes().clearHistory()
fetchMock.get('express:/institutions/domains', institutionDomainData)
await userEvent.click(button)
@@ -295,7 +295,7 @@ describe('<EmailsSection />', function () {
name: /add another email/i,
})
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
resetFetchMock()
await userEvent.click(button)
@@ -330,7 +330,7 @@ describe('<EmailsSection />', function () {
expect(universityInput.disabled).to.be.false
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
resetFetchMock()
// Select the university from dropdown
@@ -364,9 +364,11 @@ describe('<EmailsSection />', function () {
})
)
const [[, request]] = fetchMock.calls(/\/user\/emails/)
const request = fetchMock.callHistory.calls(/\/user\/emails/).at(0)
expect(JSON.parse(request?.body?.toString() || '{}')).to.deep.include({
expect(
JSON.parse(request?.options.body?.toString() || '{}')
).to.deep.include({
email: userEmailData.email,
university: {
id: userEmailData.affiliation?.institution.id,
@@ -393,7 +395,7 @@ describe('<EmailsSection />', function () {
name: /add another email/i,
})
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
resetFetchMock()
fetchMock.get('/institutions/list?country_code=de', [
@@ -446,7 +448,7 @@ describe('<EmailsSection />', function () {
name: /add another email/i,
})
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
resetFetchMock()
await userEvent.click(button)
@@ -481,7 +483,7 @@ describe('<EmailsSection />', function () {
expect(universityInput.disabled).to.be.false
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
resetFetchMock()
// Enter the university manually
@@ -516,9 +518,11 @@ describe('<EmailsSection />', function () {
await confirmCodeForEmail(userEmailData.email)
const [[, request]] = fetchMock.calls(/\/user\/emails/)
const request = fetchMock.callHistory.calls(/\/user\/emails/).at(0)
expect(JSON.parse(request?.body?.toString() || '{}')).to.deep.include({
expect(
JSON.parse(request?.options.body?.toString() || '{}')
).to.deep.include({
email: userEmailData.email,
university: {
name: newUniversity,
@@ -554,8 +558,8 @@ describe('<EmailsSection />', function () {
name: /add another email/i,
})
await fetchMock.flush(true)
fetchMock.reset()
await fetchMock.callHistory.flush(true)
fetchMock.removeRoutes().clearHistory()
fetchMock.get(
`/institutions/domains?hostname=${hostnameFirstChar}&limit=1`,
institutionDomainDataCopy
@@ -569,8 +573,8 @@ describe('<EmailsSection />', function () {
)
await userEvent.keyboard('{Tab}')
await fetchMock.flush(true)
fetchMock.reset()
await fetchMock.callHistory.flush(true)
fetchMock.removeRoutes().clearHistory()
expect(
screen.queryByRole('textbox', {
@@ -627,8 +631,8 @@ describe('<EmailsSection />', function () {
name: /add another email/i,
})
await fetchMock.flush(true)
fetchMock.reset()
await fetchMock.callHistory.flush(true)
fetchMock.removeRoutes().clearHistory()
fetchMock.get(
`/institutions/domains?hostname=${hostnameFirstChar}&limit=1`,
institutionDomainDataCopy
@@ -642,8 +646,8 @@ describe('<EmailsSection />', function () {
)
await userEvent.keyboard('{Tab}')
await fetchMock.flush(true)
fetchMock.reset()
await fetchMock.callHistory.flush(true)
fetchMock.removeRoutes().clearHistory()
screen.getByText(institutionDomainDataCopy[0].university.name)
@@ -679,8 +683,8 @@ describe('<EmailsSection />', function () {
await confirmCodeForEmail('user@autocomplete.edu')
await fetchMock.flush(true)
fetchMock.reset()
await fetchMock.callHistory.flush(true)
fetchMock.removeRoutes().clearHistory()
screen.getByText(userEmailDataCopy.affiliation.institution.name, {
exact: false,
@@ -79,12 +79,14 @@ describe('user role and institution', function () {
Object.assign(getMeta('ol-ExposedSettings'), {
hasAffiliationsFeature: true,
})
fetchMock.reset()
fetchMock.get('/user/emails?ensureAffiliation=true', [])
fetchMock.removeRoutes().clearHistory()
fetchMock.get('/user/emails?ensureAffiliation=true', [], {
name: 'get user emails',
})
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('renders affiliation name with add role/department button', function () {
@@ -122,13 +124,11 @@ describe('user role and institution', function () {
it('fetches institution data and replaces departments dropdown on add/change', async function () {
const userEmailData = userData1
fetchMock.get('/user/emails?ensureAffiliation=true', [userEmailData], {
overwriteRoutes: true,
})
fetchMock.modifyRoute('get user emails', { response: [userEmailData] })
render(<EmailsSection />)
await fetchMock.flush(true)
fetchMock.reset()
await fetchMock.callHistory.flush(true)
fetchMock.removeRoutes().clearHistory()
const fakeDepartment = 'Fake department'
const institution = userEmailData.affiliation.institution
@@ -143,8 +143,8 @@ describe('user role and institution', function () {
screen.getByRole('button', { name: /add role and department/i })
)
await fetchMock.flush(true)
fetchMock.reset()
await fetchMock.callHistory.flush(true)
fetchMock.removeRoutes().clearHistory()
fireEvent.click(screen.getByRole('textbox', { name: /department/i }))
@@ -153,9 +153,7 @@ describe('user role and institution', function () {
it('adds new role and department', async function () {
fetchMock
.get('/user/emails?ensureAffiliation=true', [userData1], {
overwriteRoutes: true,
})
.modifyRoute('get user emails', { response: [userData1] })
.get(/\/institutions\/list/, { departments: [] })
.post('/user/emails/endorse', 200)
render(<EmailsSection />)
@@ -22,11 +22,11 @@ describe('<EmailsSection />', function () {
Object.assign(getMeta('ol-ExposedSettings'), {
hasAffiliationsFeature: true,
})
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('renders translated heading', function () {
@@ -42,7 +42,7 @@ describe('<ReconfirmationInfo/>', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
this.locationStub.restore()
})
@@ -145,13 +145,13 @@ describe('<ReconfirmationInfo/>', function () {
await waitFor(() => {
expect(confirmButton.disabled).to.be.true
})
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
// the confirmation text should now be displayed
await screen.findByText(/Please check your email inbox to confirm/)
// try the resend button
fetchMock.resetHistory()
fetchMock.clearHistory()
const resendButton = await screen.findByRole('button', {
name: 'Resend confirmation email',
})
@@ -160,7 +160,7 @@ describe('<ReconfirmationInfo/>', function () {
// commented out as it's already gone by this point
// await screen.findByText(/Sending/)
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
await waitForElementToBeRemoved(() => screen.getByText('Sending…'))
await screen.findByRole('button', {
name: 'Resend confirmation email',
@@ -12,7 +12,7 @@ describe('<LeaveModalContent />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('disable delete button if form is not valid', function () {
@@ -1,7 +1,7 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { fireEvent, screen, render, waitFor } from '@testing-library/react'
import fetchMock, { FetchMockStatic } from 'fetch-mock'
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'
@@ -14,7 +14,7 @@ describe('<LeaveModalForm />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('validates form', async function () {
@@ -50,7 +50,7 @@ describe('<LeaveModalForm />', function () {
describe('submits', async function () {
let setInFlight: sinon.SinonStub
let setIsFormValid: sinon.SinonStub
let deleteMock: FetchMockStatic
let deleteMock: FetchMock
let assignStub: sinon.SinonStub
beforeEach(function () {
@@ -68,7 +68,7 @@ describe('<LeaveModalForm />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
this.locationStub.restore()
})
@@ -85,7 +85,7 @@ describe('<LeaveModalForm />', function () {
sinon.assert.calledOnce(setInFlight)
sinon.assert.calledWithMatch(setInFlight, true)
expect(deleteMock.called()).to.be.true
expect(deleteMock.callHistory.called()).to.be.true
await waitFor(() => {
sinon.assert.calledTwice(setInFlight)
sinon.assert.calledWithMatch(setInFlight, false)
@@ -105,7 +105,7 @@ describe('<LeaveModalForm />', function () {
fireEvent.submit(screen.getByLabelText('Email'))
expect(deleteMock.called()).to.be.false
expect(deleteMock.callHistory.called()).to.be.false
sinon.assert.notCalled(setInFlight)
})
})
@@ -13,7 +13,7 @@ describe('<LeaveModal />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('closes modal on cancel', async function () {
@@ -21,7 +21,7 @@ describe('<LeaversSurveyAlert/>', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('should render before the expiration date', function () {
@@ -52,7 +52,7 @@ describe('<LinkingSection />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows header', async function () {
@@ -17,7 +17,7 @@ describe('<PasswordSection />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows password managed externally message', async function () {
@@ -45,14 +45,14 @@ describe('<PasswordSection />', function () {
render(<PasswordSection />)
submitValidForm()
expect(updateMock.called()).to.be.true
expect(JSON.parse(updateMock.lastCall()![1]!.body as string)).to.deep.equal(
{
currentPassword: 'foobar',
newPassword1: 'barbaz',
newPassword2: 'barbaz',
}
)
expect(updateMock.callHistory.called()).to.be.true
expect(
JSON.parse(updateMock.callHistory.calls().at(-1)?.options.body as string)
).to.deep.equal({
currentPassword: 'foobar',
newPassword1: 'barbaz',
newPassword2: 'barbaz',
})
})
it('disables button on invalid form', async function () {
@@ -64,7 +64,7 @@ describe('<PasswordSection />', function () {
name: 'Change',
})
)
expect(updateMock.called()).to.be.false
expect(updateMock.callHistory.called()).to.be.false
})
it('validates inputs', async function () {
@@ -5,7 +5,7 @@ import fetchMock from 'fetch-mock'
describe('<SecuritySection />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows Group SSO rows in security section', async function () {
@@ -35,7 +35,7 @@ describe('SSOContext', function () {
google: 'google-id',
})
window.metaAttributesCache.set('ol-oauthProviders', mockOauthProviders)
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('should initialise subscriptions with their linked status', function () {
@@ -69,13 +69,13 @@ describe('SSOContext', function () {
it('when the provider is not linked, should do nothing', function () {
const { result } = renderSSOContext()
result.current.unlink('orcid')
expect(fetchMock.called()).to.be.false
expect(fetchMock.callHistory.called()).to.be.false
})
it('supports unmounting the component while the request is inflight', async function () {
const { result, unmount } = renderSSOContext()
result.current.unlink('google')
expect(fetchMock.called()).to.be.true
expect(fetchMock.callHistory.called()).to.be.true
unmount()
})
})
@@ -1,6 +1,8 @@
import { expect } from 'chai'
import { cloneDeep } from 'lodash'
import { renderHook } from '@testing-library/react-hooks'
import { waitFor } from '@testing-library/react'
import {
EmailContextType,
UserEmailsProvider,
@@ -26,7 +28,7 @@ const renderUserEmailsContext = () =>
describe('UserEmailContext', function () {
beforeEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
describe('context bootstrap', function () {
@@ -49,8 +51,8 @@ describe('UserEmailContext', function () {
it('should load all user emails and update the initialisation state to "success"', async function () {
fetchMock.get(/\/user\/emails/, fakeUsersData)
const { result } = renderUserEmailsContext()
await fetchMock.flush(true)
expect(fetchMock.calls()).to.have.lengthOf(1)
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,
@@ -66,10 +68,12 @@ describe('UserEmailContext', function () {
it('when loading user email fails, it should update the initialisation state to "failed"', async function () {
fetchMock.get(/\/user\/emails/, 500)
const { result } = renderUserEmailsContext()
await fetchMock.flush()
await fetchMock.callHistory.flush()
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 () {
@@ -94,12 +98,12 @@ describe('UserEmailContext', function () {
fetchMock.get(/\/user\/emails/, fakeUsersData)
const value = renderUserEmailsContext()
result = value.result
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
})
describe('getEmails()', function () {
beforeEach(async function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('should set `isLoading === true`', function () {
@@ -120,7 +124,7 @@ describe('UserEmailContext', function () {
}
fetchMock.get(/\/user\/emails/, [emailData])
result.current.getEmails()
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(result.current.state.data.byId).to.deep.equal({
'new@email.com': emailData,
})
@@ -133,7 +137,7 @@ describe('UserEmailContext', function () {
{ ...professionalUserData, samlProviderId: 'saml_provider_2' },
])
const { result } = renderUserEmailsContext()
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(result.current.state.data.linkedInstitutionIds).to.deep.equal([
'saml_provider_1',
'saml_provider_2',
@@ -257,11 +261,11 @@ describe('UserEmailContext', function () {
const affiliatedEmail2 = cloneDeep(professionalUserData)
affiliatedEmail2.emailHasInstitutionLicence = true
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock.get(/\/user\/emails/, [affiliatedEmail1, affiliatedEmail2])
result.current.getEmails()
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
// `resetLeaversSurveyExpiration` always happens after deletion
result.current.deleteEmail(affiliatedEmail1.email)
@@ -281,11 +285,11 @@ describe('UserEmailContext', function () {
const affiliatedEmail2 = cloneDeep(professionalUserData)
affiliatedEmail2.emailHasInstitutionLicence = true
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock.get(/\/user\/emails/, [affiliatedEmail1, affiliatedEmail2])
result.current.getEmails()
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
// `resetLeaversSurveyExpiration` always happens after deletion
result.current.deleteEmail(affiliatedEmail1.email)
@@ -303,11 +307,11 @@ describe('UserEmailContext', function () {
affiliatedEmail1.email = 'institution-test@example.com'
affiliatedEmail1.affiliation.pastReconfirmDate = true
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock.get(/\/user\/emails/, [confirmedUserData, affiliatedEmail1])
result.current.getEmails()
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
// `resetLeaversSurveyExpiration` always happens after deletion
result.current.deleteEmail(affiliatedEmail1.email)
@@ -323,11 +327,11 @@ describe('UserEmailContext', function () {
emailWithInstitutionLicense.email = 'institution-licensed@example.com'
emailWithInstitutionLicense.emailHasInstitutionLicence = false
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock.get(/\/user\/emails/, [emailWithInstitutionLicense])
result.current.getEmails()
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
// `resetLeaversSurveyExpiration` always happens after deletion
result.current.deleteEmail(emailWithInstitutionLicense.email)
@@ -342,11 +346,11 @@ describe('UserEmailContext', function () {
emailWithInstitutionLicense.email = 'institution-licensed@example.com'
emailWithInstitutionLicense.affiliation.pastReconfirmDate = false
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
fetchMock.get(/\/user\/emails/, [emailWithInstitutionLicense])
result.current.getEmails()
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
// `resetLeaversSurveyExpiration` always happens after deletion
result.current.deleteEmail(emailWithInstitutionLicense.email)
@@ -102,7 +102,7 @@ describe('<ShareProjectModal/>', function () {
afterEach(function () {
this.locationStub.restore()
fetchMock.restore()
fetchMock.removeRoutes().clearHistory()
cleanUpContext()
})
@@ -414,7 +414,7 @@ describe('<ShareProjectModal/>', function () {
await waitFor(() => expect(closeButton.disabled).to.be.true)
expect(fetchMock.done()).to.be.true
expect(fetchMock.callHistory.done()).to.be.true
expect(closeButton.disabled).to.be.false
})
@@ -447,7 +447,7 @@ describe('<ShareProjectModal/>', function () {
fireEvent.click(revokeButton)
await waitFor(() => expect(closeButton.disabled).to.be.true)
expect(fetchMock.done()).to.be.true
expect(fetchMock.callHistory.done()).to.be.true
expect(closeButton.disabled).to.be.false
})
@@ -484,10 +484,10 @@ describe('<ShareProjectModal/>', function () {
await waitFor(() => expect(closeButton.disabled).to.be.true)
const { body } = fetchMock.lastOptions()
const { body } = fetchMock.callHistory.calls().at(-1).options
expect(JSON.parse(body)).to.deep.equal({ privilegeLevel: 'readAndWrite' })
expect(fetchMock.done()).to.be.true
expect(fetchMock.callHistory.done()).to.be.true
expect(closeButton.disabled).to.be.false
})
@@ -525,10 +525,12 @@ describe('<ShareProjectModal/>', function () {
})
fireEvent.click(removeButton)
const url = fetchMock.lastUrl()
expect(url).to.equal('/project/test-project/users/member-viewer')
const url = fetchMock.callHistory.calls().at(-1).url
expect(url).to.equal(
'https://www.test-overleaf.com/project/test-project/users/member-viewer'
)
expect(fetchMock.done()).to.be.true
expect(fetchMock.callHistory.done()).to.be.true
})
it('changes member privileges to owner with confirmation', async function () {
@@ -573,10 +575,10 @@ describe('<ShareProjectModal/>', function () {
fireEvent.click(confirmButton)
await waitFor(() => expect(confirmButton.disabled).to.be.true)
const { body } = fetchMock.lastOptions()
const { body } = fetchMock.callHistory.calls().at(-1).options
expect(JSON.parse(body)).to.deep.equal({ user_id: 'member-viewer' })
expect(fetchMock.done()).to.be.true
expect(fetchMock.callHistory.done()).to.be.true
})
it('sends invites to input email addresses', async function () {
@@ -593,7 +595,7 @@ describe('<ShareProjectModal/>', function () {
// loading contacts
await waitFor(() => {
expect(fetchMock.called('express:/user/contacts')).to.be.true
expect(fetchMock.callHistory.called('express:/user/contacts')).to.be.true
})
// displaying a list of matching contacts
@@ -604,26 +606,29 @@ describe('<ShareProjectModal/>', function () {
// sending invitations
fetchMock.post('express:/project/:projectId/invite', (url, req) => {
const data = JSON.parse(req.body)
fetchMock.post(
'express:/project/:projectId/invite',
({ args: [url, req] }) => {
const data = JSON.parse(req.body)
if (data.email === 'a@b.c') {
return {
status: 400,
body: { errorReason: 'invalid_email' },
}
}
if (data.email === 'a@b.c') {
return {
status: 400,
body: { errorReason: 'invalid_email' },
status: 200,
body: {
invite: {
...data,
_id: data.email,
},
},
}
}
return {
status: 200,
body: {
invite: {
...data,
_id: data.email,
},
},
}
})
)
fireEvent.paste(inputElement, {
clipboardData: {
@@ -643,22 +648,24 @@ describe('<ShareProjectModal/>', function () {
let calls
await waitFor(
() => {
calls = fetchMock.calls('express:/project/:projectId/invite')
calls = fetchMock.callHistory.calls(
'express:/project/:projectId/invite'
)
expect(calls).to.have.length(4)
},
{ timeout: 5000 } // allow time for delay between each request
)
expect(calls[0][1].body).to.equal(
expect(calls[0].args[1].body).to.equal(
JSON.stringify({ email: 'test@example.com', privileges: 'readOnly' })
)
expect(calls[1][1].body).to.equal(
expect(calls[1].args[1].body).to.equal(
JSON.stringify({ email: 'foo@example.com', privileges: 'readOnly' })
)
expect(calls[2][1].body).to.equal(
expect(calls[2].args[1].body).to.equal(
JSON.stringify({ email: 'bar@example.com', privileges: 'readOnly' })
)
expect(calls[3][1].body).to.equal(
expect(calls[3].args[1].body).to.equal(
JSON.stringify({ email: 'a@b.c', privileges: 'readOnly' })
)
@@ -752,7 +759,7 @@ describe('<ShareProjectModal/>', function () {
// loading contacts
await waitFor(() => {
expect(fetchMock.called('express:/user/contacts')).to.be.true
expect(fetchMock.callHistory.called('express:/user/contacts')).to.be.true
})
const [inputElement] = await screen.findAllByLabelText('Add people')
@@ -766,19 +773,15 @@ describe('<ShareProjectModal/>', function () {
})
fireEvent.blur(inputElement)
fetchMock.postOnce(
'express:/project/:projectId/invite',
{
status: 400,
body: { errorReason },
},
{ overwriteRoutes: true }
)
fetchMock.postOnce('express:/project/:projectId/invite', {
status: 400,
body: { errorReason },
})
expect(submitButton.disabled).to.be.false
await userEvent.click(submitButton)
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
await fetchMock.callHistory.flush(true)
expect(fetchMock.callHistory.done()).to.be.true
}
await respondWithError('cannot_invite_non_user')
@@ -820,7 +823,7 @@ describe('<ShareProjectModal/>', function () {
fireEvent.click(enableButton)
await waitFor(() => expect(enableButton.disabled).to.be.true)
const { body: tokenBody } = fetchMock.lastOptions()
const { body: tokenBody } = fetchMock.callHistory.calls().at(-1).options
expect(JSON.parse(tokenBody)).to.deep.equal({
publicAccessLevel: 'tokenBased',
})
@@ -840,7 +843,7 @@ describe('<ShareProjectModal/>', function () {
fireEvent.click(disableButton)
await waitFor(() => expect(disableButton.disabled).to.be.true)
const { body: privateBody } = fetchMock.lastOptions()
const { body: privateBody } = fetchMock.callHistory.calls().at(-1).options
expect(JSON.parse(privateBody)).to.deep.equal({
publicAccessLevel: 'private',
})
@@ -865,7 +868,7 @@ describe('<ShareProjectModal/>', function () {
// Wait for contacts to load
await waitFor(() => {
expect(fetchMock.called('express:/user/contacts')).to.be.true
expect(fetchMock.callHistory.called('express:/user/contacts')).to.be.true
})
// Enter a prefix that matches a contact
@@ -919,7 +922,7 @@ describe('<ShareProjectModal/>', function () {
// Wait for contacts to load
await waitFor(() => {
expect(fetchMock.called('express:/user/contacts')).to.be.true
expect(fetchMock.callHistory.called('express:/user/contacts')).to.be.true
})
// Enter a prefix that matches a contact
@@ -954,7 +957,7 @@ describe('<ShareProjectModal/>', function () {
// Wait for contacts to load
await waitFor(() => {
expect(fetchMock.called('express:/user/contacts')).to.be.true
expect(fetchMock.callHistory.called('express:/user/contacts')).to.be.true
})
// Enter a prefix that matches a contact
@@ -988,7 +991,7 @@ describe('<ShareProjectModal/>', function () {
// Wait for contacts to load
await waitFor(() => {
expect(fetchMock.called('express:/user/contacts')).to.be.true
expect(fetchMock.callHistory.called('express:/user/contacts')).to.be.true
})
// Enter a prefix that matches a contact
@@ -51,7 +51,7 @@ describe('<GroupSubscriptionMemberships />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('renders all group subscriptions not managed', function () {
@@ -129,7 +129,7 @@ describe('<GroupSubscriptionMemberships />', function () {
fireEvent.click(this.leaveNowButton)
expect(leaveGroupApiMock.called()).to.be.true
expect(leaveGroupApiMock.callHistory.called()).to.be.true
await waitFor(() => {
expect(reloadStub).to.have.been.called
})
@@ -37,7 +37,7 @@ describe('<ManagedInstitutions />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('renders all managed institutions', function () {
@@ -98,7 +98,9 @@ describe('<ManagedInstitutions />', function () {
const unsubscribeLink = screen.getByText('Unsubscribe')
await fireEvent.click(unsubscribeLink)
await waitFor(() => expect(fetchMock.called(unsubscribeUrl)).to.be.true)
await waitFor(
() => expect(fetchMock.callHistory.called(unsubscribeUrl)).to.be.true
)
await waitFor(() => {
expect(screen.getByText('Subscribe')).to.exist
@@ -122,13 +124,14 @@ describe('<ManagedInstitutions />', function () {
</SplitTestProvider>
)
const subscribeLink = screen.getByText('Subscribe')
await fireEvent.click(subscribeLink)
await waitFor(() => expect(fetchMock.called(subscribeUrl)).to.be.true)
const subscribeLink = await screen.findByText('Subscribe')
await waitFor(() => {
expect(screen.getByText('Unsubscribe')).to.exist
})
await fireEvent.click(subscribeLink)
await waitFor(
() => expect(fetchMock.callHistory.called(subscribeUrl)).to.be.true
)
await screen.findByText('Unsubscribe')
})
it('renders nothing when there are no institutions', function () {
@@ -28,7 +28,7 @@ describe('<ManagedPublishers />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('renders all managed publishers', function () {
@@ -59,7 +59,7 @@ describe('<PauseSubscriptionModal />', function () {
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
this.locationStub.restore()
this.replaceStateStub.restore()
})
@@ -106,18 +106,18 @@ describe('<PersonalSubscription />', function () {
fetchMock.postOnce(reactivateSubscriptionUrl, 400)
fireEvent.click(reactivateBtn)
expect(reactivateBtn.disabled).to.be.true
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(reactivateBtn.disabled).to.be.false
expect(reloadStub).not.to.have.been.called
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
// 2nd click - success
fetchMock.postOnce(reactivateSubscriptionUrl, 200)
fireEvent.click(reactivateBtn)
await fetchMock.flush(true)
await fetchMock.callHistory.flush(true)
expect(reloadStub).to.have.been.calledOnce
expect(reactivateBtn.disabled).to.be.true
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('renders the expired dash', function () {
@@ -208,7 +208,7 @@ describe('<ActiveSubscription />', function () {
afterEach(function () {
this.locationStub.restore()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
function showConfirmCancelUI() {
@@ -38,7 +38,7 @@ describe('<ChangePlanModal />', function () {
afterEach(function () {
cleanUpContext()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
this.locationStub.restore()
})
@@ -7,7 +7,7 @@ import fetchMock from 'fetch-mock'
describe('group invite', function () {
describe('user has a personal subscription', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows option to cancel subscription', async function () {
@@ -10,7 +10,7 @@ describe('join group', function () {
window.metaAttributesCache.set('ol-inviteToken', inviteToken)
})
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
it('shows option to join subscription', async function () {
@@ -90,5 +90,5 @@ export function renderWithSubscriptionDashContext(
export function cleanUpContext() {
// @ts-ignore
delete global.recurly
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
}
@@ -7,7 +7,7 @@ import WordCountModal from '../../../../../frontend/js/features/word-count-modal
describe('<WordCountModal />', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
const contextProps = {
@@ -1,6 +1,5 @@
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import { Response } from 'node-fetch'
import {
deleteJSON,
FetchError,
@@ -12,11 +11,11 @@ import {
describe('fetchJSON', function () {
before(function () {
fetchMock.restore()
fetchMock.removeRoutes().clearHistory()
})
afterEach(function () {
fetchMock.restore()
fetchMock.removeRoutes().clearHistory()
})
const headers = {
@@ -138,17 +137,7 @@ describe('fetchJSON', function () {
})
it('handles 5xx responses without a status message', async function () {
// It's hard to make a Response object with statusText=null,
// so we need to do some monkey-work to make it happen
const response = new Response('weird scary error', {
ok: false,
status: 599,
})
Object.defineProperty(response, 'statusText', {
get: () => null,
set: () => {},
})
fetchMock.get('/test', response)
fetchMock.get('/test', { status: 599 })
return expect(getJSON('/test'))
.to.eventually.be.rejectedWith('Unexpected Error: 599')
@@ -139,7 +139,7 @@ describe('ProjectSnapshot', function () {
mockLatestChunk()
mockBlobs(['main.tex', 'hello.txt'])
await snapshot.refresh()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
}
describe('after initialization', function () {
@@ -176,7 +176,7 @@ describe('ProjectSnapshot', function () {
mockChanges()
mockBlobs(['goodbye.txt'])
await snapshot.refresh()
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
}
describe('after refresh', function () {
@@ -184,7 +184,7 @@ describe('ProjectSnapshot', function () {
beforeEach(refreshSnapshot)
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
describe('getDocPaths()', function () {
@@ -214,7 +214,7 @@ describe('ProjectSnapshot', function () {
describe('concurrency', function () {
afterEach(function () {
fetchMock.reset()
fetchMock.removeRoutes().clearHistory()
})
specify('two concurrent inits', async function () {
@@ -226,9 +226,9 @@ describe('ProjectSnapshot', function () {
await Promise.all([snapshot.refresh(), snapshot.refresh()])
// The first request initializes, the second request loads changes
expect(fetchMock.calls('flush')).to.have.length(2)
expect(fetchMock.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.calls('changes-1')).to.have.length(1)
expect(fetchMock.callHistory.calls('flush')).to.have.length(2)
expect(fetchMock.callHistory.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.callHistory.calls('changes-1')).to.have.length(1)
})
specify('three concurrent inits', async function () {
@@ -245,9 +245,9 @@ describe('ProjectSnapshot', function () {
// The first request initializes, the second and third are combined and
// load changes
expect(fetchMock.calls('flush')).to.have.length(2)
expect(fetchMock.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.calls('changes-1')).to.have.length(1)
expect(fetchMock.callHistory.calls('flush')).to.have.length(2)
expect(fetchMock.callHistory.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.callHistory.calls('changes-1')).to.have.length(1)
})
specify('two concurrent inits - first fails', async function () {
@@ -262,9 +262,9 @@ describe('ProjectSnapshot', function () {
// The first init fails, but the second succeeds
expect(results.filter(r => r.status === 'fulfilled')).to.have.length(1)
expect(fetchMock.calls('flush')).to.have.length(2)
expect(fetchMock.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.calls('changes-1')).to.have.length(0)
expect(fetchMock.callHistory.calls('flush')).to.have.length(2)
expect(fetchMock.callHistory.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.callHistory.calls('changes-1')).to.have.length(0)
})
specify('three concurrent inits - second fails', async function () {
@@ -285,10 +285,10 @@ describe('ProjectSnapshot', function () {
// The first init succeeds, the two queued requests fail, the last request
// succeeds
expect(results.filter(r => r.status === 'fulfilled')).to.have.length(1)
expect(fetchMock.calls('flush')).to.have.length(3)
expect(fetchMock.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.calls('changes-1')).to.have.length(1)
expect(fetchMock.calls('changes-2')).to.have.length(0)
expect(fetchMock.callHistory.calls('flush')).to.have.length(3)
expect(fetchMock.callHistory.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.callHistory.calls('changes-1')).to.have.length(1)
expect(fetchMock.callHistory.calls('changes-2')).to.have.length(0)
})
specify('two concurrent load changes', async function () {
@@ -304,10 +304,10 @@ describe('ProjectSnapshot', function () {
await Promise.all([snapshot.refresh(), snapshot.refresh()])
// One init, two load changes
expect(fetchMock.calls('flush')).to.have.length(3)
expect(fetchMock.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.calls('changes-1')).to.have.length(1)
expect(fetchMock.calls('changes-2')).to.have.length(1)
expect(fetchMock.callHistory.calls('flush')).to.have.length(3)
expect(fetchMock.callHistory.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.callHistory.calls('changes-1')).to.have.length(1)
expect(fetchMock.callHistory.calls('changes-2')).to.have.length(1)
})
specify('three concurrent load changes', async function () {
@@ -327,10 +327,10 @@ describe('ProjectSnapshot', function () {
])
// One init, two load changes (the two last are queued and combined)
expect(fetchMock.calls('flush')).to.have.length(3)
expect(fetchMock.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.calls('changes-1')).to.have.length(1)
expect(fetchMock.calls('changes-2')).to.have.length(1)
expect(fetchMock.callHistory.calls('flush')).to.have.length(3)
expect(fetchMock.callHistory.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.callHistory.calls('changes-1')).to.have.length(1)
expect(fetchMock.callHistory.calls('changes-2')).to.have.length(1)
})
specify('two concurrent load changes - first fails', async function () {
@@ -350,10 +350,10 @@ describe('ProjectSnapshot', function () {
// One init, one load changes fails, the second succeeds
expect(results.filter(r => r.status === 'fulfilled')).to.have.length(1)
expect(fetchMock.calls('flush')).to.have.length(3)
expect(fetchMock.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.calls('changes-1')).to.have.length(1)
expect(fetchMock.calls('changes-2')).to.have.length(0)
expect(fetchMock.callHistory.calls('flush')).to.have.length(3)
expect(fetchMock.callHistory.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.callHistory.calls('changes-1')).to.have.length(1)
expect(fetchMock.callHistory.calls('changes-2')).to.have.length(0)
})
specify('three concurrent load changes - second fails', async function () {
@@ -378,10 +378,10 @@ describe('ProjectSnapshot', function () {
// One init, one load changes succeeds, the second and third are combined
// and fail, the last request succeeds
expect(results.filter(r => r.status === 'fulfilled')).to.have.length(1)
expect(fetchMock.calls('flush')).to.have.length(4)
expect(fetchMock.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.calls('changes-1')).to.have.length(1)
expect(fetchMock.calls('changes-2')).to.have.length(1)
expect(fetchMock.callHistory.calls('flush')).to.have.length(4)
expect(fetchMock.callHistory.calls('latest-chunk')).to.have.length(1)
expect(fetchMock.callHistory.calls('changes-1')).to.have.length(1)
expect(fetchMock.callHistory.calls('changes-2')).to.have.length(1)
})
})
})
@@ -13,7 +13,7 @@ describe('useAbortController', function () {
}
beforeEach(function () {
fetchMock.restore()
fetchMock.removeRoutes().clearHistory()
status = {
loading: false,
@@ -23,7 +23,7 @@ describe('useAbortController', function () {
})
after(function () {
fetchMock.restore()
fetchMock.removeRoutes().clearHistory()
})
function AbortableRequest({ url }: { url: string }) {
@@ -80,8 +80,8 @@ describe('useAbortController', function () {
unmount()
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
await fetchMock.callHistory.flush(true)
expect(fetchMock.callHistory.done()).to.be.true
// wait for Promises to be resolved
await new Promise(resolve => setTimeout(resolve, 0))