From 241709c710865ccfb57ae3fdf7b2ecd45f89ee11 Mon Sep 17 00:00:00 2001
From: David <33458145+davidmcpowell@users.noreply.github.com>
Date: Wed, 13 Aug 2025 10:21:06 +0100
Subject: [PATCH] Merge pull request #27828 from
overleaf/dp-modal-test-typescript
Convert clone project, share project and dictionary modal tests to typescript
GitOrigin-RevId: 058eecef6054a40dff7c3697fcd908bb60e39b6b
---
....test.jsx => clone-project-modal.test.tsx} | 35 +++-
....jsx => dictionary-modal-content.spec.tsx} | 2 +-
.../review-panel/review-panel.spec.tsx | 3 +-
....test.jsx => share-project-modal.test.tsx} | 193 ++++++++++++------
.../frontend/helpers/editor-providers.tsx | 6 +-
5 files changed, 163 insertions(+), 76 deletions(-)
rename services/web/test/frontend/features/clone-project-modal/components/{clone-project-modal.test.jsx => clone-project-modal.test.tsx} (84%)
rename services/web/test/frontend/features/dictionary/components/{dictionary-modal-content.spec.jsx => dictionary-modal-content.spec.tsx} (98%)
rename services/web/test/frontend/features/share-project-modal/components/{share-project-modal.test.jsx => share-project-modal.test.tsx} (83%)
diff --git a/services/web/test/frontend/features/clone-project-modal/components/clone-project-modal.test.jsx b/services/web/test/frontend/features/clone-project-modal/components/clone-project-modal.test.tsx
similarity index 84%
rename from services/web/test/frontend/features/clone-project-modal/components/clone-project-modal.test.jsx
rename to services/web/test/frontend/features/clone-project-modal/components/clone-project-modal.test.tsx
index 0f523349e6..faabe254ea 100644
--- a/services/web/test/frontend/features/clone-project-modal/components/clone-project-modal.test.jsx
+++ b/services/web/test/frontend/features/clone-project-modal/components/clone-project-modal.test.tsx
@@ -58,10 +58,14 @@ describe('', function () {
contextProps
)
- const cancelButton = await screen.findByRole('button', { name: 'Cancel' })
+ const cancelButton: HTMLButtonElement = await screen.findByRole('button', {
+ name: 'Cancel',
+ })
expect(cancelButton.disabled).to.be.false
- const submitButton = await screen.findByRole('button', { name: 'Copy' })
+ const submitButton: HTMLButtonElement = await screen.findByRole('button', {
+ name: 'Copy',
+ })
expect(submitButton.disabled).to.be.false
const input = await screen.getByLabelText('New Name')
@@ -81,14 +85,21 @@ describe('', function () {
await fetchMock.callHistory.flush(true)
expect(fetchMock.callHistory.done()).to.be.true
- const { url, options } = fetchMock.callHistory
+
+ const callLog = fetchMock.callHistory
.calls('express:/project/:projectId/clone')
.at(-1)
+
+ expect(callLog).to.exist
+
+ const { url, options } = callLog!
expect(url).to.equal(
'https://www.test-overleaf.com/project/project-1/clone'
)
- expect(JSON.parse(options.body)).to.deep.equal({
+ expect(options.body).to.exist
+
+ expect(JSON.parse(options.body as string)).to.deep.equal({
projectName: 'A Cloned Project',
tags: [],
})
@@ -126,10 +137,14 @@ describe('', function () {
contextProps
)
- const button = await screen.findByRole('button', { name: 'Copy' })
+ const button: HTMLButtonElement = await screen.findByRole('button', {
+ name: 'Copy',
+ })
expect(button.disabled).to.be.false
- const cancelButton = await screen.findByRole('button', { name: 'Cancel' })
+ const cancelButton: HTMLButtonElement = await screen.findByRole('button', {
+ name: 'Cancel',
+ })
expect(cancelButton.disabled).to.be.false
fireEvent.click(button)
@@ -163,10 +178,14 @@ describe('', function () {
contextProps
)
- const button = await screen.findByRole('button', { name: 'Copy' })
+ const button: HTMLButtonElement = await screen.findByRole('button', {
+ name: 'Copy',
+ })
expect(button.disabled).to.be.false
- const cancelButton = await screen.findByRole('button', { name: 'Cancel' })
+ const cancelButton: HTMLButtonElement = await screen.findByRole('button', {
+ name: 'Cancel',
+ })
expect(cancelButton.disabled).to.be.false
fireEvent.click(button)
diff --git a/services/web/test/frontend/features/dictionary/components/dictionary-modal-content.spec.jsx b/services/web/test/frontend/features/dictionary/components/dictionary-modal-content.spec.tsx
similarity index 98%
rename from services/web/test/frontend/features/dictionary/components/dictionary-modal-content.spec.jsx
rename to services/web/test/frontend/features/dictionary/components/dictionary-modal-content.spec.tsx
index c8cdd931b3..82aa383bf5 100644
--- a/services/web/test/frontend/features/dictionary/components/dictionary-modal-content.spec.jsx
+++ b/services/web/test/frontend/features/dictionary/components/dictionary-modal-content.spec.tsx
@@ -3,7 +3,7 @@ import { EditorProviders } from '../../../helpers/editor-providers'
import { learnedWords } from '@/features/source-editor/extensions/spelling/learned-words'
describe('', function () {
- let originalLearnedWords
+ let originalLearnedWords: Set
beforeEach(function () {
cy.then(() => {
diff --git a/services/web/test/frontend/features/review-panel/review-panel.spec.tsx b/services/web/test/frontend/features/review-panel/review-panel.spec.tsx
index fcad15969b..aadefbe8b3 100644
--- a/services/web/test/frontend/features/review-panel/review-panel.spec.tsx
+++ b/services/web/test/frontend/features/review-panel/review-panel.spec.tsx
@@ -9,6 +9,7 @@ import { mockScope } from '../source-editor/helpers/mock-scope'
import { TestContainer } from '../source-editor/helpers/test-container'
import { docId } from '../source-editor/helpers/mock-doc'
import { mockProject } from '../source-editor/helpers/mock-project'
+import { UserId } from '@ol-types/user'
const userData = {
avatar_text: 'User',
@@ -874,7 +875,7 @@ describe(' for free users', function () {
it.skip('opens subscription page after clicking on `try it for free`', function () {})
it('shows `ask project owner to upgrade` message', function () {
- mountEditor('other-user-id')
+ mountEditor('other-user-id' as UserId)
cy.findByRole('dialog').within(() => {
cy.findByText(
'Please ask the project owner to upgrade to use track changes'
diff --git a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.tsx
similarity index 83%
rename from services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx
rename to services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.tsx
index 548423e90f..a1f6d829c1 100644
--- a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx
+++ b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.tsx
@@ -1,7 +1,7 @@
import { expect } from 'chai'
import sinon from 'sinon'
-import { screen, fireEvent, waitFor } from '@testing-library/react'
-import fetchMock from 'fetch-mock'
+import { screen, Screen, fireEvent, waitFor } from '@testing-library/react'
+import fetchMock, { CallLog } from 'fetch-mock'
import userEvent from '@testing-library/user-event'
import ShareProjectModal from '../../../../../frontend/js/features/share-project-modal/components/share-project-modal'
@@ -14,8 +14,17 @@ import {
} from '../../../helpers/editor-providers'
import { location } from '@/shared/components/location'
import { useProjectContext } from '@/shared/context/project-context'
+import {
+ ProjectMember,
+ ProjectMetadata,
+} from '@/shared/context/types/project-metadata'
+import { UserId } from '@ol-types/user'
+import { PublicAccessLevel } from '@ol-types/public-access-level'
-async function changePrivilegeLevel(screen, { current, next }) {
+async function changePrivilegeLevel(
+ screen: Screen,
+ { current, next }: { current: string; next: string }
+) {
const select = screen.getByDisplayValue(current)
fireEvent.click(select)
const option = screen.getByRole('option', {
@@ -24,7 +33,7 @@ async function changePrivilegeLevel(screen, { current, next }) {
fireEvent.click(option)
}
-const shareModalProjectDefaults = Object.assign({}, projectDefaults, {
+const testProjectOverrides: Partial = {
_id: 'test-project',
name: 'Test Project',
features: {
@@ -34,10 +43,20 @@ const shareModalProjectDefaults = Object.assign({}, projectDefaults, {
owner: {
_id: USER_ID,
email: USER_EMAIL,
+ first_name: 'Test',
+ last_name: 'Owner',
+ privileges: 'owner',
+ signUpDate: new Date('2025-07-07').toISOString(),
},
-})
+}
-function createContextProps(projectOverrides) {
+const shareModalProjectDefaults: ProjectMetadata = Object.assign(
+ {},
+ projectDefaults,
+ testProjectOverrides
+)
+
+function createContextProps(projectOverrides?: Partial) {
const project = Object.assign({}, shareModalProjectDefaults, projectOverrides)
return { providers: { ProjectProvider: makeProjectProvider(project) } }
}
@@ -88,6 +107,7 @@ describe('', function () {
const modalProps = {
show: true,
handleHide: sinon.stub(),
+ handleOpen: sinon.stub(),
}
beforeEach(function () {
@@ -204,11 +224,13 @@ describe('', function () {
it('displays actions for project-owners', async function () {
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
- const invites = [
+ const invites: ProjectMember[] = [
{
- _id: 'invited-author',
+ _id: 'invited-author' as UserId,
email: 'invited-author@example.com',
privileges: 'readAndWrite',
+ first_name: 'Invited',
+ last_name: 'Author',
},
]
@@ -223,11 +245,13 @@ describe('', function () {
})
it('hides actions from non-project-owners when link sharing on', async function () {
- const invites = [
+ const invites: ProjectMember[] = [
{
- _id: 'invited-author',
+ _id: 'invited-author' as UserId,
email: 'invited-author@example.com',
privileges: 'readAndWrite',
+ first_name: 'Invited',
+ last_name: 'Author',
},
]
@@ -251,11 +275,13 @@ describe('', function () {
})
it('hides actions from non-project-owners when link sharing off', async function () {
- const invites = [
+ const invites: ProjectMember[] = [
{
- _id: 'invited-author',
+ _id: 'invited-author' as UserId,
email: 'invited-author@example.com',
privileges: 'readAndWrite',
+ first_name: 'Invited',
+ last_name: 'Author',
},
]
@@ -300,29 +326,37 @@ describe('', function () {
})
it('displays project members and invites', async function () {
- const members = [
+ const members: ProjectMember[] = [
{
- _id: 'member-author',
+ _id: 'member-author' as UserId,
email: 'member-author@example.com',
privileges: 'readAndWrite',
+ first_name: 'Member',
+ last_name: 'Author',
},
{
- _id: 'member-viewer',
+ _id: 'member-viewer' as UserId,
email: 'member-viewer@example.com',
privileges: 'readOnly',
+ first_name: 'Member',
+ last_name: 'Viewer',
},
]
- const invites = [
+ const invites: ProjectMember[] = [
{
- _id: 'invited-author',
+ _id: 'invited-author' as UserId,
email: 'invited-author@example.com',
privileges: 'readAndWrite',
+ first_name: 'Invited',
+ last_name: 'Author',
},
{
- _id: 'invited-viewer',
+ _id: 'invited-viewer' as UserId,
email: 'invited-viewer@example.com',
privileges: 'readOnly',
+ first_name: 'Invited',
+ last_name: 'Viewer',
},
]
@@ -361,11 +395,13 @@ describe('', function () {
204
)
- const invites = [
+ const invites: ProjectMember[] = [
{
- _id: 'invited-author',
+ _id: 'invited-author' as UserId,
email: 'invited-author@example.com',
privileges: 'readAndWrite',
+ first_name: 'Invited',
+ last_name: 'Author',
},
]
@@ -381,21 +417,27 @@ describe('', function () {
const resendButton = screen.getByRole('button', { name: 'Resend' })
fireEvent.click(resendButton)
- await waitFor(() => expect(closeButton.disabled).to.be.true)
+ await waitFor(
+ () => expect((closeButton as HTMLButtonElement).disabled).to.be.true
+ )
expect(fetchMock.callHistory.done()).to.be.true
- await waitFor(() => expect(closeButton.disabled).to.be.false)
+ await waitFor(
+ () => expect((closeButton as HTMLButtonElement).disabled).to.be.false
+ )
})
it('revokes an invite', async function () {
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
fetchMock.deleteOnce('express:/project/:projectId/invite/:inviteId', 204)
- const invites = [
+ const invites: ProjectMember[] = [
{
- _id: 'invited-author',
+ _id: 'invited-author' as UserId,
email: 'invited-author@example.com',
privileges: 'readAndWrite',
+ first_name: 'Invited',
+ last_name: 'Author',
},
]
@@ -410,21 +452,27 @@ describe('', function () {
const revokeButton = screen.getByRole('button', { name: 'Revoke' })
fireEvent.click(revokeButton)
- await waitFor(() => expect(closeButton.disabled).to.be.true)
+ await waitFor(
+ () => expect((closeButton as HTMLButtonElement).disabled).to.be.true
+ )
expect(fetchMock.callHistory.done()).to.be.true
- await waitFor(() => expect(closeButton.disabled).to.be.false)
+ await waitFor(
+ () => expect((closeButton as HTMLButtonElement).disabled).to.be.false
+ )
})
it('changes member privileges to read + write', async function () {
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
fetchMock.putOnce('express:/project/:projectId/users/:userId', 204)
- const members = [
+ const members: ProjectMember[] = [
{
- _id: 'member-viewer',
+ _id: 'member-viewer' as UserId,
email: 'member-viewer@example.com',
privileges: 'readOnly',
+ first_name: 'Member',
+ last_name: 'Viewer',
},
]
@@ -443,24 +491,32 @@ describe('', function () {
await changePrivilegeLevel(screen, { current: 'Viewer', next: 'Editor' })
- await waitFor(() => expect(closeButton.disabled).to.be.true)
+ await waitFor(
+ () => expect((closeButton as HTMLButtonElement).disabled).to.be.true
+ )
- const { body } = fetchMock.callHistory.calls().at(-1).options
- expect(JSON.parse(body)).to.deep.equal({ privilegeLevel: 'readAndWrite' })
+ const body = fetchMock.callHistory.calls().at(-1)?.options?.body
+ expect(JSON.parse(body as string)).to.deep.equal({
+ privilegeLevel: 'readAndWrite',
+ })
expect(fetchMock.callHistory.done()).to.be.true
- await waitFor(() => expect(closeButton.disabled).to.be.false)
+ await waitFor(
+ () => expect((closeButton as HTMLButtonElement).disabled).to.be.false
+ )
})
it('removes a member from the project', async function () {
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
fetchMock.deleteOnce('express:/project/:projectId/users/:userId', 204)
- const members = [
+ const members: ProjectMember[] = [
{
- _id: 'member-viewer',
+ _id: 'member-viewer' as UserId,
email: 'member-viewer@example.com',
privileges: 'readOnly',
+ first_name: 'Member',
+ last_name: 'Viewer',
},
]
@@ -482,7 +538,7 @@ describe('', function () {
})
fireEvent.click(removeButton)
- const url = fetchMock.callHistory.calls().at(-1).url
+ const url = fetchMock.callHistory.calls().at(-1)?.url
expect(url).to.equal(
'https://www.test-overleaf.com/project/test-project/users/member-viewer'
)
@@ -494,11 +550,13 @@ describe('', function () {
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
fetchMock.postOnce('express:/project/:projectId/transfer-ownership', 204)
- const members = [
+ const members: ProjectMember[] = [
{
- _id: 'member-viewer',
+ _id: 'member-viewer' as UserId,
email: 'member-viewer@example.com',
privileges: 'readOnly',
+ first_name: 'Member',
+ last_name: 'Viewer',
},
]
@@ -517,19 +575,22 @@ describe('', function () {
screen.getByText((_, node) => {
return (
+ node !== null &&
node.textContent ===
- 'Are you sure you want to make member-viewer@example.com the owner of Test Project?'
+ 'Are you sure you want to make member-viewer@example.com the owner of Test Project?'
)
})
- const confirmButton = screen.getByRole('button', {
+ const confirmButton: HTMLButtonElement = screen.getByRole('button', {
name: 'Change owner',
})
fireEvent.click(confirmButton)
await waitFor(() => expect(confirmButton.disabled).to.be.true)
- const { body } = fetchMock.callHistory.calls().at(-1).options
- expect(JSON.parse(body)).to.deep.equal({ user_id: 'member-viewer' })
+ const body = fetchMock.callHistory.calls().at(-1)?.options?.body
+ expect(JSON.parse(body as string)).to.deep.equal({
+ user_id: 'member-viewer',
+ })
expect(fetchMock.callHistory.done()).to.be.true
})
@@ -560,7 +621,7 @@ describe('', function () {
fetchMock.post(
'express:/project/:projectId/invite',
({ args: [, req] }) => {
- const data = JSON.parse(req.body)
+ const data = JSON.parse((req as { body: string }).body)
if (data.email === 'a@b.c') {
return {
@@ -596,7 +657,7 @@ describe('', function () {
const submitButton = screen.getByRole('button', { name: 'Invite' })
await userEvent.click(submitButton)
- let calls
+ let calls: CallLog[] = []
await waitFor(
() => {
calls = fetchMock.callHistory.calls(
@@ -607,16 +668,16 @@ describe('', function () {
{ timeout: 5000 } // allow time for delay between each request
)
- expect(calls[0].args[1].body).to.equal(
+ expect((calls[0].args[1] as { body: string }).body).to.equal(
JSON.stringify({ email: 'test@example.com', privileges: 'readOnly' })
)
- expect(calls[1].args[1].body).to.equal(
+ expect((calls[1].args[1] as { body: string }).body).to.equal(
JSON.stringify({ email: 'foo@example.com', privileges: 'readOnly' })
)
- expect(calls[2].args[1].body).to.equal(
+ expect((calls[2].args[1] as { body: string }).body).to.equal(
JSON.stringify({ email: 'bar@example.com', privileges: 'readOnly' })
)
- expect(calls[3].args[1].body).to.equal(
+ expect((calls[3].args[1] as { body: string }).body).to.equal(
JSON.stringify({ email: 'a@b.c', privileges: 'readOnly' })
)
@@ -652,9 +713,9 @@ describe('', function () {
const reviewerOption = screen.getByText('Reviewer').closest('button')
const viewerOption = screen.getByText('Viewer').closest('button')
- expect(editorOption.classList.contains('disabled')).to.be.true
- expect(reviewerOption.classList.contains('disabled')).to.be.true
- expect(viewerOption.classList.contains('disabled')).to.be.false
+ expect(editorOption?.classList.contains('disabled')).to.be.true
+ expect(reviewerOption?.classList.contains('disabled')).to.be.true
+ expect(viewerOption?.classList.contains('disabled')).to.be.false
screen.getByText(
/Upgrade to add more collaborators and access collaboration features like track changes and full project history/
@@ -671,9 +732,11 @@ describe('', function () {
},
members: [
{
- _id: 'reviewer-id',
+ _id: 'reviewer-id' as UserId,
email: 'reviewer@example.com',
privileges: 'review',
+ first_name: 'Reviewer',
+ last_name: 'LastName',
},
],
})
@@ -688,9 +751,9 @@ describe('', function () {
const reviewerOption = screen.getByText('Reviewer').closest('button')
const viewerOption = screen.getByText('Viewer').closest('button')
- expect(editorOption.classList.contains('disabled')).to.be.true
- expect(reviewerOption.classList.contains('disabled')).to.be.true
- expect(viewerOption.classList.contains('disabled')).to.be.false
+ expect(editorOption?.classList.contains('disabled')).to.be.true
+ expect(reviewerOption?.classList.contains('disabled')).to.be.true
+ expect(viewerOption?.classList.contains('disabled')).to.be.false
screen.getByText(
/Upgrade to add more collaborators and access collaboration features like track changes and full project history/
@@ -714,9 +777,11 @@ describe('', function () {
const [inputElement] = await screen.findAllByLabelText('Add people')
- const submitButton = screen.getByRole('button', { name: 'Invite' })
+ const submitButton: HTMLButtonElement = screen.getByRole('button', {
+ name: 'Invite',
+ })
- const respondWithError = async function (errorReason) {
+ const respondWithError = async function (errorReason: string) {
fireEvent.focus(inputElement)
fireEvent.change(inputElement, {
target: { value: 'invited-author-1@example.com' },
@@ -760,11 +825,11 @@ describe('', function () {
fetchMock.get(`/project/${shareModalProjectDefaults._id}/tokens`, {})
fetchMock.post('express:/project/:projectId/settings/admin', 204)
- let setPublicAccessLevel = function () {}
+ let setPublicAccessLevel = function (_: PublicAccessLevel) {}
function WrappedModal() {
const { updateProject } = useProjectContext()
- setPublicAccessLevel = publicAccessLevel => {
+ setPublicAccessLevel = (publicAccessLevel: PublicAccessLevel) => {
updateProject({ publicAccessLevel })
}
return
@@ -779,14 +844,14 @@ describe('', function () {
await screen.findByText('Link sharing is off')
- const enableButton = await screen.findByRole('button', {
+ const enableButton: HTMLButtonElement = await screen.findByRole('button', {
name: 'Turn on link sharing',
})
fireEvent.click(enableButton)
await waitFor(() => expect(enableButton.disabled).to.be.true)
- const { body: tokenBody } = fetchMock.callHistory.calls().at(-1).options
- expect(JSON.parse(tokenBody)).to.deep.equal({
+ const tokenBody = fetchMock.callHistory.calls().at(-1)?.options.body
+ expect(JSON.parse(tokenBody as string)).to.deep.equal({
publicAccessLevel: 'tokenBased',
})
@@ -796,14 +861,14 @@ describe('', function () {
setPublicAccessLevel('tokenBased')
await screen.findByText('Link sharing is on')
- const disableButton = await screen.findByRole('button', {
+ const disableButton: HTMLButtonElement = await screen.findByRole('button', {
name: 'Turn off link sharing',
})
fireEvent.click(disableButton)
await waitFor(() => expect(disableButton.disabled).to.be.true)
- const { body: privateBody } = fetchMock.callHistory.calls().at(-1).options
- expect(JSON.parse(privateBody)).to.deep.equal({
+ const privateBody = fetchMock.callHistory.calls().at(-1)?.options.body
+ expect(JSON.parse(privateBody as string)).to.deep.equal({
publicAccessLevel: 'private',
})
diff --git a/services/web/test/frontend/helpers/editor-providers.tsx b/services/web/test/frontend/helpers/editor-providers.tsx
index 16df207b88..e3ac0042d6 100644
--- a/services/web/test/frontend/helpers/editor-providers.tsx
+++ b/services/web/test/frontend/helpers/editor-providers.tsx
@@ -52,7 +52,7 @@ import { ProjectCompiler } from '../../../types/project-settings'
// using magic strings
export const PROJECT_ID = 'project123'
export const PROJECT_NAME = 'project-name'
-export const USER_ID = '123abd'
+export const USER_ID = '123abd' as UserId
export const USER_EMAIL = 'testuser@example.com'
const defaultUserSettings = {
@@ -91,7 +91,7 @@ export type EditorProvidersProps = {
providers?: Record>>
}
-export const projectDefaults = {
+export const projectDefaults: ProjectMetadata = {
_id: PROJECT_ID,
name: PROJECT_NAME,
owner: {
@@ -125,6 +125,8 @@ export const projectDefaults = {
compiler: 'pdflatex' as ProjectCompiler,
members: [],
invites: [],
+ trackChangesState: {} as Record,
+ spellCheckLanguage: 'en',
}
/**