mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 09:09:36 +02:00
* Replace placeholders with labels * Add 'Close dialog' label to modal close button to distinguish from footer Close button * Add and translate heading on the not found page * Update textarea to have id matching label's for attribute Simplify test for template description textarea * Label PDF zoom level dropdown button * Improve test selectors to use semantic roles and accessible names GitOrigin-RevId: d215ddca30ddf844cfffbcf0e528a601b134d772
519 lines
16 KiB
TypeScript
519 lines
16 KiB
TypeScript
import { v4 as uuid } from 'uuid'
|
|
import {
|
|
isExcludedBySharding,
|
|
startWith,
|
|
reloadWith,
|
|
STARTUP_TIMEOUT,
|
|
} from './helpers/config'
|
|
import { ensureUserExists, login } from './helpers/login'
|
|
import {
|
|
createProject,
|
|
enableLinkSharing,
|
|
getSpamSafeProjectName,
|
|
openProjectByName,
|
|
openProjectViaLinkSharingAsAnon,
|
|
openProjectViaLinkSharingAsUser,
|
|
shareProjectByEmailAndAcceptInviteViaDash,
|
|
shareProjectByEmailAndAcceptInviteViaEmail,
|
|
} from './helpers/project'
|
|
import { prepareWaitForNextCompileSlot } from './helpers/compile'
|
|
import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
|
|
|
|
describe('Project Sharing', function () {
|
|
if (isExcludedBySharding('PRO_CUSTOM_4')) return
|
|
ensureUserExists({ email: 'user@example.com' })
|
|
startWith({ withDataDir: true, pro: true })
|
|
|
|
let projectName: string
|
|
let recompile: () => void
|
|
let waitForCompile: (triggerCompile: () => void) => void
|
|
beforeWithReRunOnTestRetry(() => {
|
|
projectName = getSpamSafeProjectName()
|
|
;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
|
|
setupTestProject()
|
|
})
|
|
|
|
beforeEach(function () {
|
|
// Always start with a fresh session
|
|
cy.session([uuid()], () => {})
|
|
})
|
|
|
|
let linkSharingReadOnly: string
|
|
let linkSharingReadAndWrite: string
|
|
|
|
function setupTestProject() {
|
|
login('user@example.com')
|
|
waitForCompile(() => {
|
|
createProject(projectName)
|
|
})
|
|
|
|
// Add chat message
|
|
cy.findByRole('button', { name: 'Chat' }).click()
|
|
// wait for lazy loading of the chat pane
|
|
cy.findByRole('complementary', { name: 'Chat' }).findByText(
|
|
'Send your first message to your collaborators'
|
|
)
|
|
cy.findByLabelText('Send a message to your collaborators…').type(
|
|
'New Chat Message{enter}'
|
|
)
|
|
|
|
// Get link sharing links
|
|
enableLinkSharing().then(
|
|
({ linkSharingReadOnly: ro, linkSharingReadAndWrite: rw }) => {
|
|
linkSharingReadAndWrite = rw
|
|
linkSharingReadOnly = ro
|
|
}
|
|
)
|
|
}
|
|
|
|
function expectContentReadOnlyAccess() {
|
|
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
'contain.text',
|
|
'\\maketitle'
|
|
)
|
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
'have.attr',
|
|
'contenteditable',
|
|
'false'
|
|
)
|
|
}
|
|
|
|
function expectContentWriteAccess() {
|
|
const section = `Test Section ${uuid()}`
|
|
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
|
// wait for the editor to finish loading
|
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
'contain.text',
|
|
'\\maketitle'
|
|
)
|
|
// the editor should be writable
|
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
'have.attr',
|
|
'contenteditable',
|
|
'true'
|
|
)
|
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
|
|
cy.findByText('\\maketitle').parent().click()
|
|
cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`)
|
|
})
|
|
// should have written
|
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
'contain.text',
|
|
`\\section{${section}}`
|
|
)
|
|
// check PDF
|
|
recompile()
|
|
cy.findByRole('region', { name: 'PDF preview and logs' }).within(() => {
|
|
cy.findByLabelText(/Page.*1/i).should('be.visible')
|
|
cy.findByText(projectName).should('be.visible')
|
|
})
|
|
cy.findByRole('region', { name: 'PDF preview and logs' }).within(() => {
|
|
cy.findByLabelText(/Page.*1/i).should('be.visible')
|
|
cy.contains(section)
|
|
})
|
|
}
|
|
|
|
function expectNoAccess() {
|
|
// try read only access link
|
|
cy.visit(linkSharingReadOnly)
|
|
cy.url().should('match', /\/login/)
|
|
|
|
// Cypress bugs: cypress resolves the link-sharing link outside the browser, and it carries over the hash of the link-sharing link to the login page redirect (bug 1).
|
|
// Effectively, cypress then instructs the browser to change the page from /login#read-only-hash to /login#read-and-write-hash.
|
|
// This is turn does not trigger a "page load", but rather just "scrolling", which in turn trips up the "page loaded" detection in cypress (bug 2).
|
|
// Work around this by navigating away from the /login page in between checks.
|
|
cy.visit('/user/password/reset')
|
|
|
|
// try read and write access link
|
|
cy.visit(linkSharingReadAndWrite)
|
|
cy.url().should('match', /\/login/)
|
|
}
|
|
|
|
function expectChatAccess() {
|
|
cy.findByRole('button', { name: 'Chat' }).click()
|
|
cy.findByRole('complementary', { name: 'Chat' }).findByText(
|
|
'New Chat Message'
|
|
)
|
|
}
|
|
|
|
function expectHistoryAccess() {
|
|
cy.findByRole('button', { name: 'History' }).click()
|
|
// The input is not clickable due to being visually hidden, click its label instead
|
|
cy.findByRole('complementary', {
|
|
name: 'Project history and labels',
|
|
}).within(() => {
|
|
cy.findByRole('group', {
|
|
name: 'Show all of the project history or only labelled versions.',
|
|
}).within(() => {
|
|
cy.findByText('All history').click()
|
|
})
|
|
cy.findByRole('radio', { name: 'Labels' }).should('not.be.checked')
|
|
cy.findByRole('radio', { name: 'All history' }).should('be.checked')
|
|
})
|
|
cy.findByText(/\\begin\{document}/)
|
|
cy.findByRole('complementary', {
|
|
name: 'Project history and labels',
|
|
}).within(() => {
|
|
cy.findAllByTestId('history-version-metadata-users')
|
|
.last()
|
|
.should('have.text', 'user')
|
|
})
|
|
cy.findByRole('button', { name: 'Back to editor' }).click()
|
|
}
|
|
|
|
function expectNoChatAccess() {
|
|
cy.findByRole('button', { name: 'Layout' }) // wait for lazy loading
|
|
cy.findByRole('button', { name: 'Chat' }).should('not.exist')
|
|
}
|
|
|
|
function expectNoHistoryAccess() {
|
|
cy.findByRole('button', { name: 'Layout' }) // wait for lazy loading
|
|
cy.findByRole('button', { name: 'History' }).should('not.exist')
|
|
}
|
|
|
|
function expectCommentAccess() {
|
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
'contain.text',
|
|
'\\maketitle'
|
|
)
|
|
|
|
cy.findByText('\\maketitle').parent().dblclick()
|
|
|
|
cy.findByRole('button', { name: 'Add comment' }).should('be.visible')
|
|
|
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).click()
|
|
}
|
|
|
|
function expectNoCommentAccess() {
|
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
|
'contain.text',
|
|
'\\maketitle'
|
|
)
|
|
|
|
cy.findByText('\\maketitle').parent().dblclick()
|
|
|
|
cy.findByRole('button', { name: 'Add comment' }).should('not.exist')
|
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).click()
|
|
}
|
|
|
|
function expectFullReadOnlyAccess() {
|
|
expectContentReadOnlyAccess()
|
|
expectChatAccess()
|
|
expectHistoryAccess()
|
|
expectNoCommentAccess()
|
|
}
|
|
|
|
function expectRestrictedReadOnlyAccess() {
|
|
expectContentReadOnlyAccess()
|
|
expectNoChatAccess()
|
|
expectNoHistoryAccess()
|
|
expectNoCommentAccess()
|
|
}
|
|
|
|
function expectFullReadAndWriteAccess() {
|
|
expectContentWriteAccess()
|
|
expectChatAccess()
|
|
expectHistoryAccess()
|
|
expectCommentAccess()
|
|
}
|
|
|
|
function expectAnonymousReadAndWriteAccess() {
|
|
expectContentWriteAccess()
|
|
expectChatAccess()
|
|
expectHistoryAccess()
|
|
expectNoCommentAccess()
|
|
}
|
|
|
|
function expectProjectDashboardEntry() {
|
|
cy.visit('/project')
|
|
cy.findByText(projectName)
|
|
}
|
|
|
|
function expectEditAuthoredAs(author: string) {
|
|
cy.findByRole('button', { name: 'History' }).click()
|
|
cy.findByRole('complementary', {
|
|
name: 'Project history and labels',
|
|
}).within(() => {
|
|
cy.findAllByTestId('history-version-metadata-users')
|
|
.first()
|
|
.should('contain.text', author) // might have other edits in the same group
|
|
})
|
|
}
|
|
describe('via email', function () {
|
|
const email = 'collaborator-email@example.com'
|
|
ensureUserExists({ email })
|
|
|
|
beforeEach(function () {
|
|
login('user@example.com')
|
|
shareProjectByEmailAndAcceptInviteViaEmail(projectName, email, 'Viewer')
|
|
})
|
|
|
|
it('should grant the collaborator read access', function () {
|
|
expectFullReadOnlyAccess()
|
|
expectProjectDashboardEntry()
|
|
})
|
|
})
|
|
|
|
describe('read only', function () {
|
|
const email = 'collaborator-ro@example.com'
|
|
ensureUserExists({ email })
|
|
|
|
beforeWithReRunOnTestRetry(() => {
|
|
login('user@example.com')
|
|
shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Viewer')
|
|
})
|
|
|
|
it('should grant the collaborator read access', function () {
|
|
login(email)
|
|
openProjectByName(projectName)
|
|
expectFullReadOnlyAccess()
|
|
expectProjectDashboardEntry()
|
|
})
|
|
})
|
|
|
|
describe('read and write', function () {
|
|
const email = 'collaborator-rw@example.com'
|
|
ensureUserExists({ email })
|
|
|
|
beforeWithReRunOnTestRetry(() => {
|
|
login('user@example.com')
|
|
shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Editor')
|
|
})
|
|
|
|
it('should grant the collaborator write access', function () {
|
|
login(email)
|
|
openProjectByName(projectName)
|
|
expectFullReadAndWriteAccess()
|
|
expectEditAuthoredAs('You')
|
|
expectProjectDashboardEntry()
|
|
})
|
|
})
|
|
|
|
describe('token access', function () {
|
|
describe('logged in', function () {
|
|
describe('read only', function () {
|
|
const email = 'collaborator-link-ro@example.com'
|
|
ensureUserExists({ email })
|
|
|
|
it('should grant restricted read access', function () {
|
|
login(email)
|
|
openProjectViaLinkSharingAsUser(
|
|
linkSharingReadOnly,
|
|
projectName,
|
|
email
|
|
)
|
|
expectRestrictedReadOnlyAccess()
|
|
expectProjectDashboardEntry()
|
|
})
|
|
})
|
|
|
|
describe('read and write', function () {
|
|
const email = 'collaborator-link-rw@example.com'
|
|
ensureUserExists({ email })
|
|
|
|
it('should grant full write access', function () {
|
|
login(email)
|
|
openProjectViaLinkSharingAsUser(
|
|
linkSharingReadAndWrite,
|
|
projectName,
|
|
email
|
|
)
|
|
expectFullReadAndWriteAccess()
|
|
expectEditAuthoredAs('You')
|
|
expectProjectDashboardEntry()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=false', function () {
|
|
describe('wrap startup', function () {
|
|
startWith({
|
|
pro: true,
|
|
vars: {
|
|
OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false',
|
|
},
|
|
withDataDir: true,
|
|
})
|
|
it('should block access', function () {
|
|
expectNoAccess()
|
|
})
|
|
})
|
|
|
|
describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', function () {
|
|
startWith({
|
|
pro: true,
|
|
vars: {
|
|
OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false',
|
|
OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
|
|
},
|
|
withDataDir: true,
|
|
})
|
|
it('should block access', function () {
|
|
expectNoAccess()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=true', function () {
|
|
describe('wrap startup', function () {
|
|
startWith({
|
|
pro: true,
|
|
vars: {
|
|
OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
|
|
},
|
|
withDataDir: true,
|
|
})
|
|
it('should grant read access with read link', function () {
|
|
openProjectViaLinkSharingAsAnon(linkSharingReadOnly)
|
|
expectRestrictedReadOnlyAccess()
|
|
})
|
|
|
|
it('should prompt for login with write link', function () {
|
|
cy.visit(linkSharingReadAndWrite)
|
|
cy.url().should('match', /\/login/)
|
|
})
|
|
})
|
|
|
|
describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', function () {
|
|
startWith({
|
|
pro: true,
|
|
vars: {
|
|
OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
|
|
OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
|
|
},
|
|
withDataDir: true,
|
|
})
|
|
|
|
it('should grant read access with read link', function () {
|
|
openProjectViaLinkSharingAsAnon(linkSharingReadOnly)
|
|
expectRestrictedReadOnlyAccess()
|
|
})
|
|
|
|
it('should grant write access with write link', function () {
|
|
openProjectViaLinkSharingAsAnon(linkSharingReadAndWrite)
|
|
expectAnonymousReadAndWriteAccess()
|
|
expectEditAuthoredAs('Anonymous')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('with OVERLEAF_DISABLE_LINK_SHARING=true', function () {
|
|
const email = 'collaborator-email@example.com'
|
|
ensureUserExists({ email })
|
|
|
|
const invitedEmail = 'invited-email@example.com'
|
|
ensureUserExists({ email: invitedEmail })
|
|
|
|
const retainedViewerEmail = 'collaborator-retained-viewer@example.com'
|
|
ensureUserExists({ email: retainedViewerEmail })
|
|
|
|
const retainedEditorEmail = 'collaborator-retained-editor@example.com'
|
|
ensureUserExists({ email: retainedEditorEmail })
|
|
|
|
// Link-sharing urls have to be created before disabling link sharing.
|
|
// We use the `beforeEach` hook to reload the server with link sharing
|
|
// disabled **after** the initial setup which happens in the `before`
|
|
// block. The `before` hook always runs prior to the `beforeEach` hook.
|
|
|
|
// Set up retained access before disabling link sharing
|
|
before(function () {
|
|
// Set up retained viewer access
|
|
login(retainedViewerEmail)
|
|
openProjectViaLinkSharingAsUser(
|
|
linkSharingReadOnly,
|
|
projectName,
|
|
retainedViewerEmail
|
|
)
|
|
|
|
// Set up retained editor access
|
|
login(retainedEditorEmail)
|
|
openProjectViaLinkSharingAsUser(
|
|
linkSharingReadAndWrite,
|
|
projectName,
|
|
retainedEditorEmail
|
|
)
|
|
})
|
|
|
|
beforeEach(function () {
|
|
this.timeout(STARTUP_TIMEOUT) // Increase timeout for server reload
|
|
|
|
return cy.wrap(
|
|
reloadWith({
|
|
pro: true,
|
|
vars: {
|
|
OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
|
|
OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
|
|
OVERLEAF_DISABLE_LINK_SHARING: 'true',
|
|
},
|
|
withDataDir: true,
|
|
}),
|
|
{ timeout: STARTUP_TIMEOUT }
|
|
)
|
|
})
|
|
|
|
it('should not display link sharing in the sharing modal', function () {
|
|
login('user@example.com')
|
|
openProjectByName(projectName)
|
|
cy.findByRole('navigation', {
|
|
name: 'Project actions',
|
|
})
|
|
.findByRole('button', { name: 'Share' })
|
|
.click()
|
|
cy.findByRole('button', { name: 'Turn on link sharing' }).should(
|
|
'not.exist'
|
|
)
|
|
})
|
|
|
|
it('should block new access to read-only link shared projects', function () {
|
|
login(email)
|
|
|
|
// Test read-only link returns 404
|
|
cy.request({
|
|
url: linkSharingReadOnly,
|
|
failOnStatusCode: false,
|
|
}).then(response => {
|
|
expect(response.status).to.eq(404)
|
|
})
|
|
})
|
|
|
|
it('should block new access to read-write link shared projects', function () {
|
|
login(email)
|
|
|
|
// Test read-write link returns 404
|
|
cy.request({
|
|
url: linkSharingReadAndWrite,
|
|
failOnStatusCode: false,
|
|
}).then(response => {
|
|
expect(response.status).to.eq(404)
|
|
})
|
|
})
|
|
|
|
it('should continue to allow email sharing', function () {
|
|
login('user@example.com')
|
|
shareProjectByEmailAndAcceptInviteViaEmail(
|
|
projectName,
|
|
invitedEmail,
|
|
'Viewer'
|
|
)
|
|
expectFullReadOnlyAccess()
|
|
expectProjectDashboardEntry()
|
|
})
|
|
|
|
it('should retain read-only access when project was joined via link before link sharing was turned off', function () {
|
|
login(retainedViewerEmail)
|
|
openProjectByName(projectName)
|
|
expectRestrictedReadOnlyAccess()
|
|
expectProjectDashboardEntry()
|
|
})
|
|
|
|
it('should retain read-write access when project was joined via link before link sharing was turned off', function () {
|
|
login(retainedEditorEmail)
|
|
openProjectByName(projectName)
|
|
expectFullReadAndWriteAccess()
|
|
expectProjectDashboardEntry()
|
|
})
|
|
})
|
|
})
|
|
})
|