first commit
This commit is contained in:
@@ -0,0 +1,680 @@
|
||||
import { useState } from 'react'
|
||||
import ToggleSwitch from '../../../../../frontend/js/features/history/components/change-list/toggle-switch'
|
||||
import ChangeList from '../../../../../frontend/js/features/history/components/change-list/change-list'
|
||||
import {
|
||||
EditorProviders,
|
||||
USER_EMAIL,
|
||||
USER_ID,
|
||||
} from '../../../helpers/editor-providers'
|
||||
import { HistoryProvider } from '../../../../../frontend/js/features/history/context/history-context'
|
||||
import { updates } from '../fixtures/updates'
|
||||
import { labels } from '../fixtures/labels'
|
||||
import { formatTime, relativeDate } from '@/features/utils/format-date'
|
||||
|
||||
const mountWithEditorProviders = (
|
||||
component: React.ReactNode,
|
||||
scope: Record<string, unknown> = {},
|
||||
props: Record<string, unknown> = {}
|
||||
) => {
|
||||
cy.mount(
|
||||
<EditorProviders scope={scope} {...props}>
|
||||
<HistoryProvider>
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<div className="history-react">{component}</div>
|
||||
</div>
|
||||
</HistoryProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
}
|
||||
|
||||
describe('change list (Bootstrap 5)', function () {
|
||||
const scope = {
|
||||
ui: { view: 'history', pdfLayout: 'sideBySide', chatOpen: true },
|
||||
}
|
||||
|
||||
const waitForData = () => {
|
||||
cy.wait('@updates')
|
||||
cy.wait('@labels')
|
||||
cy.wait('@diff')
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
cy.intercept('GET', '/project/*/updates*', {
|
||||
body: updates,
|
||||
}).as('updates')
|
||||
cy.intercept('GET', '/project/*/labels', {
|
||||
body: labels,
|
||||
}).as('labels')
|
||||
cy.intercept('GET', '/project/*/filetree/diff*', {
|
||||
body: { diff: [{ pathname: 'main.tex' }, { pathname: 'name.tex' }] },
|
||||
}).as('diff')
|
||||
window.metaAttributesCache.set('ol-inactiveTutorials', [
|
||||
'react-history-buttons-tutorial',
|
||||
])
|
||||
})
|
||||
|
||||
describe('toggle switch', function () {
|
||||
it('renders switch buttons', function () {
|
||||
mountWithEditorProviders(
|
||||
<ToggleSwitch labelsOnly={false} setLabelsOnly={() => {}} />
|
||||
)
|
||||
|
||||
cy.findByLabelText(/all history/i)
|
||||
cy.findByLabelText(/labels/i)
|
||||
})
|
||||
|
||||
it('toggles "all history" and "labels" buttons', function () {
|
||||
function ToggleSwitchWrapped({ labelsOnly }: { labelsOnly: boolean }) {
|
||||
const [labelsOnlyLocal, setLabelsOnlyLocal] = useState(labelsOnly)
|
||||
return (
|
||||
<ToggleSwitch
|
||||
labelsOnly={labelsOnlyLocal}
|
||||
setLabelsOnly={setLabelsOnlyLocal}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
mountWithEditorProviders(<ToggleSwitchWrapped labelsOnly={false} />)
|
||||
|
||||
cy.findByLabelText(/all history/i).as('all-history')
|
||||
cy.findByLabelText(/labels/i).as('labels')
|
||||
cy.get('@all-history').should('be.checked')
|
||||
cy.get('@labels').should('not.be.checked')
|
||||
cy.get('@labels').click({ force: true })
|
||||
cy.get('@all-history').should('not.be.checked')
|
||||
cy.get('@labels').should('be.checked')
|
||||
})
|
||||
})
|
||||
|
||||
describe('tags', function () {
|
||||
it('renders tags', function () {
|
||||
mountWithEditorProviders(<ChangeList />, scope, {
|
||||
user: {
|
||||
id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
isAdmin: true,
|
||||
},
|
||||
})
|
||||
waitForData()
|
||||
|
||||
cy.findByLabelText(/all history/i).click({ force: true })
|
||||
cy.findAllByTestId('history-version-details').as('details')
|
||||
cy.get('@details').should('have.length', 5)
|
||||
// start with 2nd details entry, as first has no tags
|
||||
cy.get('@details')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.findAllByTestId('history-version-badge').as('tags')
|
||||
})
|
||||
cy.get('@tags').should('have.length', 2)
|
||||
cy.get('@tags').eq(0).should('contain.text', 'tag-2')
|
||||
cy.get('@tags').eq(1).should('contain.text', 'tag-1')
|
||||
// should have delete buttons
|
||||
cy.get('@tags').each(tag =>
|
||||
cy.wrap(tag).within(() => {
|
||||
cy.findByRole('button', { name: /delete/i })
|
||||
})
|
||||
)
|
||||
// 3rd details entry
|
||||
cy.get('@details')
|
||||
.eq(2)
|
||||
.within(() => {
|
||||
cy.findAllByTestId('history-version-badge').should('have.length', 0)
|
||||
})
|
||||
// 4th details entry
|
||||
cy.get('@details')
|
||||
.eq(3)
|
||||
.within(() => {
|
||||
cy.findAllByTestId('history-version-badge').as('tags')
|
||||
})
|
||||
cy.get('@tags').should('have.length', 2)
|
||||
cy.get('@tags').eq(0).should('contain.text', 'tag-4')
|
||||
cy.get('@tags').eq(1).should('contain.text', 'tag-3')
|
||||
// should not have delete buttons
|
||||
cy.get('@tags').each(tag =>
|
||||
cy.wrap(tag).within(() => {
|
||||
cy.findByRole('button', { name: /delete/i }).should('not.exist')
|
||||
})
|
||||
)
|
||||
cy.findByLabelText(/labels/i).click({ force: true })
|
||||
cy.findAllByTestId('history-version-details').as('details')
|
||||
// first details on labels is always "current version", start testing on second
|
||||
cy.get('@details').should('have.length', 3)
|
||||
cy.get('@details')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.findAllByTestId('history-version-badge').as('tags')
|
||||
})
|
||||
cy.get('@tags').should('have.length', 2)
|
||||
cy.get('@tags').eq(0).should('contain.text', 'tag-2')
|
||||
cy.get('@tags').eq(1).should('contain.text', 'tag-1')
|
||||
cy.get('@details')
|
||||
.eq(2)
|
||||
.within(() => {
|
||||
cy.findAllByTestId('history-version-badge').as('tags')
|
||||
})
|
||||
cy.get('@tags').should('have.length', 3)
|
||||
cy.get('@tags').eq(0).should('contain.text', 'tag-5')
|
||||
cy.get('@tags').eq(1).should('contain.text', 'tag-4')
|
||||
cy.get('@tags').eq(2).should('contain.text', 'tag-3')
|
||||
})
|
||||
|
||||
it('deletes tag', function () {
|
||||
mountWithEditorProviders(<ChangeList />, scope, {
|
||||
user: {
|
||||
id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
isAdmin: true,
|
||||
},
|
||||
})
|
||||
waitForData()
|
||||
|
||||
cy.findByLabelText(/all history/i).click({ force: true })
|
||||
|
||||
const labelToDelete = 'tag-2'
|
||||
cy.findAllByTestId('history-version-details').eq(1).as('details')
|
||||
cy.get('@details').within(() => {
|
||||
cy.findAllByTestId('history-version-badge').eq(0).as('tag')
|
||||
})
|
||||
cy.get('@tag').should('contain.text', labelToDelete)
|
||||
cy.get('@tag').within(() => {
|
||||
cy.findByRole('button', { name: /delete/i }).as('delete-btn')
|
||||
})
|
||||
cy.get('@delete-btn').click()
|
||||
cy.findByRole('dialog').as('modal')
|
||||
cy.get('@modal').within(() => {
|
||||
cy.findByRole('heading', { name: /delete label/i })
|
||||
})
|
||||
cy.get('@modal').contains(
|
||||
new RegExp(
|
||||
`are you sure you want to delete the following label "${labelToDelete}"?`,
|
||||
'i'
|
||||
)
|
||||
)
|
||||
cy.get('@modal').within(() => {
|
||||
cy.findByRole('button', { name: /cancel/i }).click()
|
||||
})
|
||||
cy.findByRole('dialog').should('not.exist')
|
||||
cy.get('@delete-btn').click()
|
||||
cy.findByRole('dialog').as('modal')
|
||||
cy.intercept('DELETE', '/project/*/labels/*', {
|
||||
statusCode: 500,
|
||||
}).as('delete')
|
||||
cy.get('@modal').within(() => {
|
||||
cy.findByRole('button', { name: /delete/i }).click()
|
||||
})
|
||||
cy.wait('@delete')
|
||||
cy.get('@modal').within(() => {
|
||||
cy.findByRole('alert').within(() => {
|
||||
cy.contains(/sorry, something went wrong/i)
|
||||
})
|
||||
})
|
||||
cy.findByText(labelToDelete).should('have.length', 1)
|
||||
|
||||
cy.intercept('DELETE', '/project/*/labels/*', {
|
||||
statusCode: 204,
|
||||
}).as('delete')
|
||||
cy.get('@modal').within(() => {
|
||||
cy.findByRole('button', { name: /delete/i }).click()
|
||||
})
|
||||
cy.wait('@delete')
|
||||
cy.findByText(labelToDelete).should('not.exist')
|
||||
})
|
||||
|
||||
it('verifies that selecting the same list item will not trigger a new diff', function () {
|
||||
mountWithEditorProviders(<ChangeList />, scope, {
|
||||
user: {
|
||||
id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
isAdmin: true,
|
||||
},
|
||||
})
|
||||
waitForData()
|
||||
|
||||
const stub = cy.stub().as('diffStub')
|
||||
cy.intercept('GET', '/project/*/filetree/diff*', stub).as('diff')
|
||||
|
||||
cy.findAllByTestId('history-version-details').eq(2).as('details')
|
||||
cy.get('@details').click() // 1st click
|
||||
cy.wait('@diff')
|
||||
cy.get('@details').click() // 2nd click
|
||||
cy.get('@diffStub').should('have.been.calledOnce')
|
||||
})
|
||||
})
|
||||
|
||||
describe('all history', function () {
|
||||
beforeEach(function () {
|
||||
mountWithEditorProviders(<ChangeList />, scope, {
|
||||
user: {
|
||||
id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
isAdmin: true,
|
||||
},
|
||||
})
|
||||
waitForData()
|
||||
})
|
||||
|
||||
it('shows grouped versions date', function () {
|
||||
cy.findByText(relativeDate(updates.updates[0].meta.end_ts))
|
||||
cy.findByText(relativeDate(updates.updates[1].meta.end_ts))
|
||||
})
|
||||
|
||||
it('shows the date of the version', function () {
|
||||
cy.findAllByTestId('history-version-details')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.findByTestId('history-version-metadata-time').should(
|
||||
'have.text',
|
||||
formatTime(updates.updates[0].meta.end_ts, 'Do MMMM, h:mm a')
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('shows change action', function () {
|
||||
cy.findAllByTestId('history-version-details')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.findByTestId('history-version-change-action').should(
|
||||
'have.text',
|
||||
'Created'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('shows changed document name', function () {
|
||||
cy.findAllByTestId('history-version-details')
|
||||
.eq(2)
|
||||
.within(() => {
|
||||
cy.findByTestId('history-version-change-doc').should(
|
||||
'have.text',
|
||||
updates.updates[2].pathnames[0]
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('shows users', function () {
|
||||
cy.findAllByTestId('history-version-details')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.findByTestId('history-version-metadata-users')
|
||||
.should('contain.text', 'You')
|
||||
.and('contain.text', updates.updates[1].meta.users[1].first_name)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('labels only', function () {
|
||||
beforeEach(function () {
|
||||
mountWithEditorProviders(<ChangeList />, scope, {
|
||||
user: {
|
||||
id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
isAdmin: true,
|
||||
},
|
||||
})
|
||||
waitForData()
|
||||
cy.findByLabelText(/labels/i).click({ force: true })
|
||||
})
|
||||
|
||||
it('shows the dropdown menu item for adding new labels', function () {
|
||||
cy.findAllByTestId('history-version-details')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.findByRole('button', { name: /more actions/i }).click()
|
||||
cy.findByRole('menu').within(() => {
|
||||
cy.findByRole('menuitem', {
|
||||
name: /label this version/i,
|
||||
}).should('exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('resets from compare to view mode when switching tabs', function () {
|
||||
cy.findAllByTestId('history-version-details')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.findByRole('button', {
|
||||
name: /Compare/i,
|
||||
}).click()
|
||||
})
|
||||
cy.findByLabelText(/all history/i).click({ force: true })
|
||||
cy.findAllByTestId('history-version-details').should($versions => {
|
||||
const [selected, ...rest] = Array.from($versions)
|
||||
expect(selected).to.have.attr('data-selected', 'selected')
|
||||
expect(
|
||||
rest.every(version => version.dataset.selected === 'belowSelected')
|
||||
).to.be.true
|
||||
})
|
||||
})
|
||||
it('opens the compare drop down and compares with selected version', function () {
|
||||
cy.findByLabelText(/all history/i).click({ force: true })
|
||||
cy.findAllByTestId('history-version-details')
|
||||
.eq(3)
|
||||
.within(() => {
|
||||
cy.findByRole('button', {
|
||||
name: /compare from this version/i,
|
||||
}).click()
|
||||
})
|
||||
|
||||
cy.findAllByTestId('history-version-details')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('[aria-label="Compare"]').click()
|
||||
cy.findByRole('menu').within(() => {
|
||||
cy.findByRole('menuitem', {
|
||||
name: /compare up to this version/i,
|
||||
}).click()
|
||||
})
|
||||
})
|
||||
|
||||
cy.findAllByTestId('history-version-details').should($versions => {
|
||||
const [
|
||||
aboveSelected,
|
||||
upperSelected,
|
||||
withinSelected,
|
||||
lowerSelected,
|
||||
belowSelected,
|
||||
] = Array.from($versions)
|
||||
expect(aboveSelected).to.have.attr('data-selected', 'aboveSelected')
|
||||
expect(upperSelected).to.have.attr('data-selected', 'upperSelected')
|
||||
expect(withinSelected).to.have.attr('data-selected', 'withinSelected')
|
||||
expect(lowerSelected).to.have.attr('data-selected', 'lowerSelected')
|
||||
expect(belowSelected).to.have.attr('data-selected', 'belowSelected')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('compare mode', function () {
|
||||
beforeEach(function () {
|
||||
mountWithEditorProviders(<ChangeList />, scope, {
|
||||
user: {
|
||||
id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
isAdmin: true,
|
||||
},
|
||||
})
|
||||
waitForData()
|
||||
})
|
||||
|
||||
it('compares versions', function () {
|
||||
cy.findAllByTestId('history-version-details').should($versions => {
|
||||
const [first, ...rest] = Array.from($versions)
|
||||
expect(first).to.have.attr('data-selected', 'selected')
|
||||
rest.forEach(version =>
|
||||
// Based on the fact that we are selecting first version as we load the page
|
||||
// Every other version will be belowSelected
|
||||
expect(version).to.have.attr('data-selected', 'belowSelected')
|
||||
)
|
||||
})
|
||||
|
||||
cy.intercept('GET', '/project/*/filetree/diff*', {
|
||||
body: { diff: [{ pathname: 'main.tex' }, { pathname: 'name.tex' }] },
|
||||
}).as('compareDiff')
|
||||
|
||||
cy.findAllByTestId('history-version-details')
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.findByTestId('compare-icon-version').click()
|
||||
})
|
||||
cy.wait('@compareDiff')
|
||||
})
|
||||
})
|
||||
|
||||
describe('dropdown', function () {
|
||||
beforeEach(function () {
|
||||
mountWithEditorProviders(<ChangeList />, scope, {
|
||||
user: {
|
||||
id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
isAdmin: true,
|
||||
},
|
||||
})
|
||||
waitForData()
|
||||
})
|
||||
|
||||
it('adds badge/label', function () {
|
||||
cy.findAllByTestId('history-version-details').eq(1).as('version')
|
||||
cy.get('@version').within(() => {
|
||||
cy.findByRole('button', { name: /more actions/i }).click()
|
||||
cy.findByRole('menu').within(() => {
|
||||
cy.findByRole('menuitem', {
|
||||
name: /label this version/i,
|
||||
}).click()
|
||||
})
|
||||
})
|
||||
cy.intercept('POST', '/project/*/labels', req => {
|
||||
req.reply(200, {
|
||||
id: '64633ee158e9ef7da614c000',
|
||||
comment: req.body.comment,
|
||||
version: req.body.version,
|
||||
user_id: USER_ID,
|
||||
created_at: '2023-05-16T08:29:21.250Z',
|
||||
user_display_name: 'john.doe',
|
||||
})
|
||||
}).as('addLabel')
|
||||
const newLabel = 'my new label'
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.findByRole('heading', { name: /add label/i })
|
||||
cy.findByRole('button', { name: /cancel/i })
|
||||
cy.findByRole('button', { name: /add label/i }).should('be.disabled')
|
||||
cy.findByPlaceholderText(/new label name/i).as('input')
|
||||
cy.get('@input').type(newLabel)
|
||||
cy.findByRole('button', { name: /add label/i }).should('be.enabled')
|
||||
cy.get('@input').type('{enter}')
|
||||
})
|
||||
cy.wait('@addLabel')
|
||||
cy.get('@version').within(() => {
|
||||
cy.findAllByTestId('history-version-badge').should($badges => {
|
||||
const includes = Array.from($badges).some(badge =>
|
||||
badge.textContent?.includes(newLabel)
|
||||
)
|
||||
expect(includes).to.be.true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('downloads version', function () {
|
||||
cy.intercept('GET', '/project/*/version/*/zip', { statusCode: 200 }).as(
|
||||
'download'
|
||||
)
|
||||
cy.findAllByTestId('history-version-details')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.findByRole('button', { name: /more actions/i }).click()
|
||||
cy.findByRole('menu').within(() => {
|
||||
cy.findByRole('menuitem', {
|
||||
name: /download this version/i,
|
||||
}).click()
|
||||
})
|
||||
})
|
||||
cy.wait('@download')
|
||||
})
|
||||
})
|
||||
|
||||
describe('paywall', function () {
|
||||
const now = Date.now()
|
||||
const oneMinuteAgo = now - 60 * 1000
|
||||
const justOverADayAgo = now - 25 * 60 * 60 * 1000
|
||||
const twoDaysAgo = now - 48 * 60 * 60 * 1000
|
||||
|
||||
const updates = {
|
||||
updates: [
|
||||
{
|
||||
fromV: 3,
|
||||
toV: 4,
|
||||
meta: {
|
||||
users: [
|
||||
{
|
||||
first_name: 'john.doe',
|
||||
last_name: '',
|
||||
email: 'john.doe@test.com',
|
||||
id: '1',
|
||||
},
|
||||
],
|
||||
start_ts: oneMinuteAgo,
|
||||
end_ts: oneMinuteAgo,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [{ add: { pathname: 'name.tex' }, atV: 3 }],
|
||||
},
|
||||
{
|
||||
fromV: 1,
|
||||
toV: 3,
|
||||
meta: {
|
||||
users: [
|
||||
{
|
||||
first_name: 'bobby.lapointe',
|
||||
last_name: '',
|
||||
email: 'bobby.lapointe@test.com',
|
||||
id: '2',
|
||||
},
|
||||
],
|
||||
start_ts: justOverADayAgo,
|
||||
end_ts: justOverADayAgo - 10 * 1000,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: ['main.tex'],
|
||||
project_ops: [],
|
||||
},
|
||||
{
|
||||
fromV: 0,
|
||||
toV: 1,
|
||||
meta: {
|
||||
users: [
|
||||
{
|
||||
first_name: 'john.doe',
|
||||
last_name: '',
|
||||
email: 'john.doe@test.com',
|
||||
id: '1',
|
||||
},
|
||||
],
|
||||
start_ts: twoDaysAgo,
|
||||
end_ts: twoDaysAgo,
|
||||
},
|
||||
labels: [
|
||||
{
|
||||
id: 'label1',
|
||||
comment: 'tag-1',
|
||||
version: 0,
|
||||
user_id: USER_ID,
|
||||
created_at: justOverADayAgo,
|
||||
},
|
||||
],
|
||||
pathnames: [],
|
||||
project_ops: [{ add: { pathname: 'main.tex' }, atV: 0 }],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const labels = [
|
||||
{
|
||||
id: 'label1',
|
||||
comment: 'tag-1',
|
||||
version: 0,
|
||||
user_id: USER_ID,
|
||||
created_at: justOverADayAgo,
|
||||
user_display_name: 'john.doe',
|
||||
},
|
||||
]
|
||||
|
||||
const waitForData = () => {
|
||||
cy.wait('@updates')
|
||||
cy.wait('@labels')
|
||||
cy.wait('@diff')
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
cy.intercept('GET', '/project/*/updates*', {
|
||||
body: updates,
|
||||
}).as('updates')
|
||||
cy.intercept('GET', '/project/*/labels', {
|
||||
body: labels,
|
||||
}).as('labels')
|
||||
cy.intercept('GET', '/project/*/filetree/diff*', {
|
||||
body: { diff: [{ pathname: 'main.tex' }, { pathname: 'name.tex' }] },
|
||||
}).as('diff')
|
||||
})
|
||||
|
||||
it('shows non-owner paywall', function () {
|
||||
const scope = {
|
||||
ui: {
|
||||
view: 'history',
|
||||
pdfLayout: 'sideBySide',
|
||||
chatOpen: true,
|
||||
},
|
||||
}
|
||||
|
||||
mountWithEditorProviders(<ChangeList />, scope, {
|
||||
user: {
|
||||
id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
isAdmin: false,
|
||||
},
|
||||
})
|
||||
|
||||
waitForData()
|
||||
|
||||
cy.get('.history-paywall-prompt').should('have.length', 1)
|
||||
cy.findAllByTestId('history-version').should('have.length', 2)
|
||||
cy.get('.history-paywall-prompt button').should('not.exist')
|
||||
})
|
||||
|
||||
it('shows owner paywall', function () {
|
||||
const scope = {
|
||||
ui: {
|
||||
view: 'history',
|
||||
pdfLayout: 'sideBySide',
|
||||
chatOpen: true,
|
||||
},
|
||||
}
|
||||
|
||||
mountWithEditorProviders(<ChangeList />, scope, {
|
||||
user: {
|
||||
id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
isAdmin: false,
|
||||
},
|
||||
projectOwner: {
|
||||
_id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
},
|
||||
})
|
||||
|
||||
waitForData()
|
||||
|
||||
cy.get('.history-paywall-prompt').should('have.length', 1)
|
||||
cy.findAllByTestId('history-version').should('have.length', 2)
|
||||
cy.get('.history-paywall-prompt button').should('have.length', 1)
|
||||
})
|
||||
|
||||
it('shows all labels in free tier', function () {
|
||||
const scope = {
|
||||
ui: {
|
||||
view: 'history',
|
||||
pdfLayout: 'sideBySide',
|
||||
chatOpen: true,
|
||||
},
|
||||
}
|
||||
|
||||
mountWithEditorProviders(<ChangeList />, scope, {
|
||||
user: {
|
||||
id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
isAdmin: false,
|
||||
},
|
||||
projectOwner: {
|
||||
_id: USER_ID,
|
||||
email: USER_EMAIL,
|
||||
},
|
||||
})
|
||||
|
||||
waitForData()
|
||||
|
||||
cy.findByLabelText(/labels/i).click({ force: true })
|
||||
|
||||
// One pseudo-label for the current state, one for our label
|
||||
cy.get('.history-version-label').should('have.length', 2)
|
||||
})
|
||||
})
|
||||
})
|
@@ -0,0 +1,256 @@
|
||||
import DocumentDiffViewer from '../../../../../frontend/js/features/history/components/diff-view/document-diff-viewer'
|
||||
import { Highlight } from '../../../../../frontend/js/features/history/services/types/doc'
|
||||
import { FC } from 'react'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
|
||||
const doc = `\\documentclass{article}
|
||||
|
||||
% Language setting
|
||||
% Replace \`english' with e.g. \`spanish' to change the document language
|
||||
\\usepackage[english]{babel}
|
||||
|
||||
% Set page size and margins
|
||||
% Replace \`letterpaper' with \`a4paper' for UK/EU standard size
|
||||
\\usepackage[letterpaper,top=2cm,bottom=2cm,left=3cm,right=3cm,marginparwidth=1.75cm]{geometry}
|
||||
|
||||
% Useful packages
|
||||
\\usepackage{amsmath}
|
||||
\\usepackage{graphicx}
|
||||
\\usepackage[colorlinks=true, allcolors=blue]{hyperref}
|
||||
|
||||
\\title{Your Paper}
|
||||
\\author{You}
|
||||
|
||||
\\begin{document}
|
||||
\\maketitle
|
||||
|
||||
\\begin{abstract}
|
||||
Your abstract.
|
||||
\\end{abstract}
|
||||
|
||||
\\section{Introduction}
|
||||
|
||||
Your introduction goes here! Simply start writing your document and use the Recompile button to view the updated PDF preview. Examples of commonly used commands and features are listed below, to help you get started.
|
||||
|
||||
Once you're familiar with the editor, you can find various project settings in the Overleaf menu, accessed via the button in the very top left of the editor. To view tutorials, user guides, and further documentation, please visit our \\href{https://www.overleaf.com/learn}{help library}, or head to our plans page to \\href{https://www.overleaf.com/user/subscription/plans}{choose your plan}.
|
||||
|
||||
${'\n'.repeat(200)}
|
||||
|
||||
\\end{document}`
|
||||
|
||||
const highlights: Highlight[] = [
|
||||
{
|
||||
type: 'addition',
|
||||
range: { from: 15, to: 22 },
|
||||
hue: 200,
|
||||
label: 'Added by Wombat on Monday',
|
||||
},
|
||||
{
|
||||
type: 'deletion',
|
||||
range: { from: 27, to: 35 },
|
||||
hue: 200,
|
||||
label: 'Deleted by Wombat on Tuesday',
|
||||
},
|
||||
{
|
||||
type: 'addition',
|
||||
range: { from: doc.length - 9, to: doc.length - 1 },
|
||||
hue: 200,
|
||||
label: 'Added by Wombat on Wednesday',
|
||||
},
|
||||
]
|
||||
|
||||
const Container: FC = ({ children }) => (
|
||||
<div style={{ width: 600, height: 400 }}>{children}</div>
|
||||
)
|
||||
|
||||
const mockScope = () => {
|
||||
return {
|
||||
settings: {
|
||||
fontSize: 12,
|
||||
fontFamily: 'monaco',
|
||||
lineHeight: 'normal',
|
||||
overallTheme: '',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('document diff viewer', function () {
|
||||
it('displays highlights with hover tooltips', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<Container>
|
||||
<EditorProviders scope={scope}>
|
||||
<DocumentDiffViewer doc={doc} highlights={highlights} />
|
||||
</EditorProviders>
|
||||
</Container>
|
||||
)
|
||||
|
||||
cy.get('.ol-cm-addition-marker').should('have.length', 1)
|
||||
cy.get('.ol-cm-addition-marker').first().as('addition')
|
||||
cy.get('@addition').should('have.text', 'article')
|
||||
|
||||
cy.get('.ol-cm-deletion-marker').should('have.length', 1)
|
||||
cy.get('.ol-cm-deletion-marker').first().as('deletion')
|
||||
cy.get('@deletion').should('have.text', 'Language')
|
||||
|
||||
// Check hover tooltips
|
||||
cy.get('@addition').trigger('mouseover')
|
||||
cy.get('.ol-cm-highlight-tooltip').should('have.length', 1)
|
||||
cy.get('.ol-cm-highlight-tooltip')
|
||||
.first()
|
||||
.should('have.text', 'Added by Wombat on Monday')
|
||||
|
||||
cy.get('@deletion').trigger('mouseover')
|
||||
cy.get('.ol-cm-highlight-tooltip').should('have.length', 1)
|
||||
cy.get('.ol-cm-highlight-tooltip')
|
||||
.first()
|
||||
.should('have.text', 'Deleted by Wombat on Tuesday')
|
||||
})
|
||||
|
||||
it('displays highlights with hover tooltips for empty lines', function () {
|
||||
const scope = mockScope()
|
||||
|
||||
const doc = `1
|
||||
Addition
|
||||
|
||||
|
||||
End
|
||||
2
|
||||
Deletion
|
||||
|
||||
End
|
||||
3`
|
||||
const highlights: Highlight[] = [
|
||||
{
|
||||
type: 'addition',
|
||||
range: { from: 2, to: 16 },
|
||||
hue: 200,
|
||||
label: 'Added by Wombat on Monday',
|
||||
},
|
||||
{
|
||||
type: 'deletion',
|
||||
range: { from: 19, to: 32 },
|
||||
hue: 200,
|
||||
label: 'Deleted by Wombat on Tuesday',
|
||||
},
|
||||
]
|
||||
|
||||
cy.mount(
|
||||
<Container>
|
||||
<EditorProviders scope={scope}>
|
||||
<DocumentDiffViewer doc={doc} highlights={highlights} />
|
||||
</EditorProviders>
|
||||
</Container>
|
||||
)
|
||||
|
||||
cy.get('.ol-cm-empty-line-addition-marker').should('have.length', 2)
|
||||
cy.get('.ol-cm-empty-line-deletion-marker').should('have.length', 1)
|
||||
|
||||
// For an empty line marker, we need to trigger mouseover on the containing
|
||||
// line beause the marker itself does not trigger mouseover
|
||||
cy.get('.ol-cm-empty-line-addition-marker')
|
||||
.first()
|
||||
.parent()
|
||||
.as('firstAdditionLine')
|
||||
cy.get('.ol-cm-empty-line-addition-marker')
|
||||
.first()
|
||||
.parent()
|
||||
.as('lastAdditionLine')
|
||||
cy.get('.ol-cm-empty-line-deletion-marker')
|
||||
.last()
|
||||
.parent()
|
||||
.as('deletionLine')
|
||||
|
||||
// Check hover tooltips
|
||||
cy.get('@lastAdditionLine').trigger('mouseover')
|
||||
cy.get('.ol-cm-highlight-tooltip').should('have.length', 1)
|
||||
cy.get('.ol-cm-highlight-tooltip')
|
||||
.first()
|
||||
.should('have.text', 'Added by Wombat on Monday')
|
||||
|
||||
cy.get('@lastAdditionLine').trigger('mouseleave')
|
||||
|
||||
cy.get('@firstAdditionLine').trigger('mouseover')
|
||||
cy.get('.ol-cm-highlight-tooltip').should('have.length', 1)
|
||||
cy.get('.ol-cm-highlight-tooltip')
|
||||
.first()
|
||||
.should('have.text', 'Added by Wombat on Monday')
|
||||
|
||||
cy.get('@deletionLine').trigger('mouseover')
|
||||
cy.get('.ol-cm-highlight-tooltip').should('have.length', 1)
|
||||
cy.get('.ol-cm-highlight-tooltip')
|
||||
.first()
|
||||
.should('have.text', 'Deleted by Wombat on Tuesday')
|
||||
})
|
||||
|
||||
it("renders 'More updates' buttons", function () {
|
||||
const scope = mockScope()
|
||||
|
||||
cy.mount(
|
||||
<Container>
|
||||
<EditorProviders scope={scope}>
|
||||
<DocumentDiffViewer doc={doc} highlights={highlights} />
|
||||
</EditorProviders>
|
||||
</Container>
|
||||
)
|
||||
|
||||
cy.get('.cm-scroller').first().as('scroller')
|
||||
|
||||
// Check the initial state, which should be a "More updates below" button
|
||||
// but no "More updates above", with the editor scrolled to the top
|
||||
cy.get('.ol-cm-addition-marker').should('have.length', 1)
|
||||
cy.get('.ol-cm-deletion-marker').should('have.length', 1)
|
||||
cy.get('.previous-highlight-button').should('have.length', 0)
|
||||
cy.get('.next-highlight-button').should('have.length', 1)
|
||||
cy.get('@scroller').invoke('scrollTop').should('equal', 0)
|
||||
|
||||
// Click the "More updates below" button, which should scroll the editor,
|
||||
// and check the new state
|
||||
cy.get('.next-highlight-button').first().click()
|
||||
|
||||
cy.get('@scroller').invoke('scrollTop').should('not.equal', 0)
|
||||
cy.get('.previous-highlight-button').should('have.length', 1)
|
||||
cy.get('.next-highlight-button').should('have.length', 0)
|
||||
|
||||
// Click the "More updates above" button, which should scroll the editor up
|
||||
// but not quite to the top, and check the new state
|
||||
cy.get('.previous-highlight-button').first().click()
|
||||
cy.get('@scroller').invoke('scrollTop').should('equal', 0)
|
||||
cy.get('.previous-highlight-button').should('not.exist')
|
||||
cy.get('.next-highlight-button').should('have.length', 1)
|
||||
})
|
||||
|
||||
it('scrolls to first change', function () {
|
||||
const scope = mockScope()
|
||||
const finalHighlightOnly = highlights.slice(-1)
|
||||
|
||||
cy.mount(
|
||||
<Container>
|
||||
<EditorProviders scope={scope}>
|
||||
<DocumentDiffViewer doc={doc} highlights={finalHighlightOnly} />
|
||||
</EditorProviders>
|
||||
</Container>
|
||||
)
|
||||
|
||||
cy.get('.cm-scroller').first().invoke('scrollTop').should('not.equal', 0)
|
||||
cy.get('.ol-cm-addition-marker')
|
||||
.first()
|
||||
.then($marker => {
|
||||
cy.get('.cm-content')
|
||||
.first()
|
||||
.then($content => {
|
||||
const contentRect = $content[0].getBoundingClientRect()
|
||||
const markerRect = $marker[0].getBoundingClientRect()
|
||||
expect(markerRect.top).to.be.within(
|
||||
contentRect.top,
|
||||
contentRect.bottom
|
||||
)
|
||||
expect(markerRect.bottom).to.be.within(
|
||||
contentRect.top,
|
||||
contentRect.bottom
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@@ -0,0 +1,126 @@
|
||||
import Toolbar from '../../../../../frontend/js/features/history/components/diff-view/toolbar/toolbar'
|
||||
import { HistoryProvider } from '../../../../../frontend/js/features/history/context/history-context'
|
||||
import { HistoryContextValue } from '../../../../../frontend/js/features/history/context/types/history-context-value'
|
||||
import { Diff } from '../../../../../frontend/js/features/history/services/types/doc'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
|
||||
describe('history toolbar', function () {
|
||||
const editorProvidersScope = {
|
||||
ui: { view: 'history', pdfLayout: 'sideBySide', chatOpen: true },
|
||||
}
|
||||
|
||||
const diff: Diff = {
|
||||
binary: false,
|
||||
docDiff: {
|
||||
highlights: [
|
||||
{
|
||||
range: {
|
||||
from: 0,
|
||||
to: 3,
|
||||
},
|
||||
hue: 1,
|
||||
type: 'addition',
|
||||
label: 'label',
|
||||
},
|
||||
],
|
||||
doc: 'doc',
|
||||
},
|
||||
}
|
||||
|
||||
it('renders viewing mode', function () {
|
||||
const selection: HistoryContextValue['selection'] = {
|
||||
updateRange: {
|
||||
fromV: 3,
|
||||
toV: 6,
|
||||
fromVTimestamp: 1681413775958,
|
||||
toVTimestamp: 1681413775958,
|
||||
},
|
||||
comparing: false,
|
||||
files: [
|
||||
{
|
||||
pathname: 'main.tex',
|
||||
operation: 'edited',
|
||||
},
|
||||
{
|
||||
pathname: 'sample.bib',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'frog.jpg',
|
||||
editable: false,
|
||||
},
|
||||
],
|
||||
selectedFile: {
|
||||
pathname: 'main.tex',
|
||||
editable: true,
|
||||
},
|
||||
previouslySelectedPathname: null,
|
||||
}
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders scope={editorProvidersScope}>
|
||||
<HistoryProvider>
|
||||
<div className="history-react">
|
||||
<Toolbar diff={diff} selection={selection} />
|
||||
</div>
|
||||
</HistoryProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get('.history-react-toolbar').within(() => {
|
||||
cy.get('div:first-child').contains('Viewing 13th April')
|
||||
})
|
||||
|
||||
cy.get('.history-react-toolbar-file-info').contains('1 change in main.tex')
|
||||
})
|
||||
|
||||
it('renders comparing mode', function () {
|
||||
const selection: HistoryContextValue['selection'] = {
|
||||
updateRange: {
|
||||
fromV: 0,
|
||||
toV: 6,
|
||||
fromVTimestamp: 1681313775958,
|
||||
toVTimestamp: 1681413775958,
|
||||
},
|
||||
comparing: true,
|
||||
files: [
|
||||
{
|
||||
pathname: 'main.tex',
|
||||
operation: 'added',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'sample.bib',
|
||||
operation: 'added',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'frog.jpg',
|
||||
operation: 'added',
|
||||
editable: false,
|
||||
},
|
||||
],
|
||||
selectedFile: {
|
||||
pathname: 'main.tex',
|
||||
editable: true,
|
||||
},
|
||||
previouslySelectedPathname: null,
|
||||
}
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders scope={editorProvidersScope}>
|
||||
<HistoryProvider>
|
||||
<div className="history-react">
|
||||
<Toolbar diff={diff} selection={selection} />
|
||||
</div>
|
||||
</HistoryProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
cy.get('.history-react-toolbar').within(() => {
|
||||
cy.get('div:first-child').contains('Comparing from 12th April')
|
||||
|
||||
cy.get('div:first-child').contains('to 13th April')
|
||||
})
|
||||
})
|
||||
})
|
@@ -0,0 +1,44 @@
|
||||
import { USER_ID } from '../../../helpers/editor-providers'
|
||||
|
||||
export const labels = [
|
||||
{
|
||||
id: '643561cdfa2b2beac88f0024',
|
||||
comment: 'tag-1',
|
||||
version: 4,
|
||||
user_id: USER_ID,
|
||||
created_at: '2023-04-11T13:34:05.856Z',
|
||||
user_display_name: 'john.doe',
|
||||
},
|
||||
{
|
||||
id: '643561d1fa2b2beac88f0025',
|
||||
comment: 'tag-2',
|
||||
version: 4,
|
||||
user_id: USER_ID,
|
||||
created_at: '2023-04-11T13:34:09.280Z',
|
||||
user_display_name: 'john.doe',
|
||||
},
|
||||
{
|
||||
id: '6436bcf630293cb49e7f13a4',
|
||||
comment: 'tag-3',
|
||||
version: 1,
|
||||
user_id: '631710ab1094c5002647184e',
|
||||
created_at: '2023-04-12T14:15:18.892Z',
|
||||
user_display_name: 'bobby.lapointe',
|
||||
},
|
||||
{
|
||||
id: '6436bcf830293cb49e7f13a5',
|
||||
comment: 'tag-4',
|
||||
version: 1,
|
||||
user_id: '631710ab1094c5002647184e',
|
||||
created_at: '2023-04-12T14:15:20.814Z',
|
||||
user_display_name: 'bobby.lapointe',
|
||||
},
|
||||
{
|
||||
id: '6436bcfb30293cb49e7f13a6',
|
||||
comment: 'tag-5',
|
||||
version: 1,
|
||||
user_id: '631710ab1094c5002647184e',
|
||||
created_at: '2023-04-12T14:15:23.481Z',
|
||||
user_display_name: 'bobby.lapointe',
|
||||
},
|
||||
]
|
149
services/web/test/frontend/features/history/fixtures/updates.ts
Normal file
149
services/web/test/frontend/features/history/fixtures/updates.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { USER_ID, USER_EMAIL } from '../../../helpers/editor-providers'
|
||||
|
||||
export const updates = {
|
||||
updates: [
|
||||
{
|
||||
fromV: 5,
|
||||
toV: 6,
|
||||
meta: {
|
||||
users: [
|
||||
{
|
||||
first_name: 'testuser',
|
||||
last_name: '',
|
||||
email: USER_EMAIL,
|
||||
id: USER_ID,
|
||||
},
|
||||
{
|
||||
first_name: 'john.doe',
|
||||
last_name: '',
|
||||
email: 'john.doe@test.com',
|
||||
id: '631710ab1094c5002647184e',
|
||||
},
|
||||
],
|
||||
start_ts: 1681220036519,
|
||||
end_ts: 1681220036619,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: ['name.tex'],
|
||||
project_ops: [],
|
||||
},
|
||||
{
|
||||
fromV: 4,
|
||||
toV: 5,
|
||||
meta: {
|
||||
users: [
|
||||
{
|
||||
first_name: 'testuser',
|
||||
last_name: '',
|
||||
email: USER_EMAIL,
|
||||
id: USER_ID,
|
||||
},
|
||||
{
|
||||
first_name: 'john.doe',
|
||||
last_name: '',
|
||||
email: 'john.doe@test.com',
|
||||
id: '631710ab1094c5002647184e',
|
||||
},
|
||||
],
|
||||
start_ts: 1681220036419,
|
||||
end_ts: 1681220036419,
|
||||
},
|
||||
labels: [
|
||||
{
|
||||
id: '643561cdfa2b2beac88f0024',
|
||||
comment: 'tag-1',
|
||||
version: 4,
|
||||
user_id: USER_ID,
|
||||
created_at: '2023-04-11T13:34:05.856Z',
|
||||
},
|
||||
{
|
||||
id: '643561d1fa2b2beac88f0025',
|
||||
comment: 'tag-2',
|
||||
version: 4,
|
||||
user_id: USER_ID,
|
||||
created_at: '2023-04-11T13:34:09.280Z',
|
||||
},
|
||||
],
|
||||
pathnames: [],
|
||||
project_ops: [{ add: { pathname: 'name.tex' }, atV: 3 }],
|
||||
},
|
||||
{
|
||||
fromV: 2,
|
||||
toV: 4,
|
||||
meta: {
|
||||
users: [
|
||||
{
|
||||
first_name: 'bobby.lapointe',
|
||||
last_name: '',
|
||||
email: 'bobby.lapointe@test.com',
|
||||
id: '631710ab1094c5002647184e',
|
||||
},
|
||||
],
|
||||
start_ts: 1681220029569,
|
||||
end_ts: 1681220031589,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: ['main.tex'],
|
||||
project_ops: [],
|
||||
},
|
||||
{
|
||||
fromV: 1,
|
||||
toV: 2,
|
||||
meta: {
|
||||
users: [
|
||||
{
|
||||
first_name: 'john.doe',
|
||||
last_name: '',
|
||||
email: 'john.doe@test.com',
|
||||
id: '631710ab1094c5002647184e',
|
||||
},
|
||||
],
|
||||
start_ts: 1669218226672,
|
||||
end_ts: 1669218226672,
|
||||
},
|
||||
labels: [
|
||||
{
|
||||
id: '6436bcf630293cb49e7f13a4',
|
||||
comment: 'tag-3',
|
||||
version: 3,
|
||||
user_id: '631710ab1094c5002647184e',
|
||||
created_at: '2023-04-12T14:15:18.892Z',
|
||||
},
|
||||
{
|
||||
id: '6436bcf830293cb49e7f13a5',
|
||||
comment: 'tag-4',
|
||||
version: 3,
|
||||
user_id: '631710ab1094c5002647184e',
|
||||
created_at: '2023-04-12T14:15:20.814Z',
|
||||
},
|
||||
],
|
||||
pathnames: ['main.tex'],
|
||||
project_ops: [],
|
||||
},
|
||||
{
|
||||
fromV: 0,
|
||||
toV: 1,
|
||||
meta: {
|
||||
users: [
|
||||
{
|
||||
first_name: 'testuser',
|
||||
last_name: '',
|
||||
email: USER_EMAIL,
|
||||
id: USER_ID,
|
||||
},
|
||||
{
|
||||
first_name: 'john.doe',
|
||||
last_name: '',
|
||||
email: 'john.doe@test.com',
|
||||
id: '631710ab1094c5002647184e',
|
||||
},
|
||||
],
|
||||
start_ts: 1669218226500,
|
||||
end_ts: 1669218226600,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [{ add: { pathname: 'main.tex' }, atV: 3 }],
|
||||
},
|
||||
],
|
||||
}
|
@@ -0,0 +1,899 @@
|
||||
import { expect } from 'chai'
|
||||
import type { FileDiff } from '../../../../../frontend/js/features/history/services/types/file'
|
||||
import { autoSelectFile } from '../../../../../frontend/js/features/history/utils/auto-select-file'
|
||||
import type { User } from '../../../../../frontend/js/features/history/services/types/shared'
|
||||
import { LoadedUpdate } from '../../../../../frontend/js/features/history/services/types/update'
|
||||
import { fileFinalPathname } from '../../../../../frontend/js/features/history/utils/file-diff'
|
||||
import { getUpdateForVersion } from '../../../../../frontend/js/features/history/utils/history-details'
|
||||
|
||||
describe('autoSelectFile', function () {
|
||||
const historyUsers: User[] = [
|
||||
{
|
||||
first_name: 'first_name',
|
||||
last_name: 'last_name',
|
||||
email: 'email@overleaf.com',
|
||||
id: '6266xb6b7a366460a66186xx',
|
||||
},
|
||||
]
|
||||
|
||||
describe('for comparing version with previous', function () {
|
||||
const comparing = false
|
||||
|
||||
it('return the file with `edited` as the last operation', function () {
|
||||
const files: FileDiff[] = [
|
||||
{
|
||||
pathname: 'main.tex',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'sample.bib',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'frog.jpg',
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
pathname: 'newfile5.tex',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'newfolder1/newfolder2/newfile2.tex',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'newfolder1/newfile10.tex',
|
||||
operation: 'edited',
|
||||
},
|
||||
]
|
||||
|
||||
const updates: LoadedUpdate[] = [
|
||||
{
|
||||
fromV: 25,
|
||||
toV: 26,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680888731881,
|
||||
end_ts: 1680888731881,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: ['newfolder1/newfile10.tex'],
|
||||
project_ops: [],
|
||||
},
|
||||
{
|
||||
fromV: 23,
|
||||
toV: 25,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680888725098,
|
||||
end_ts: 1680888729123,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
rename: {
|
||||
pathname: 'newfolder1/newfile3.tex',
|
||||
newPathname: 'newfolder1/newfile10.tex',
|
||||
},
|
||||
atV: 24,
|
||||
},
|
||||
{
|
||||
rename: {
|
||||
pathname: 'newfile3.tex',
|
||||
newPathname: 'newfolder1/newfile3.tex',
|
||||
},
|
||||
atV: 23,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fromV: 22,
|
||||
toV: 23,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680888721015,
|
||||
end_ts: 1680888721015,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: ['newfile3.tex'],
|
||||
project_ops: [],
|
||||
},
|
||||
{
|
||||
fromV: 19,
|
||||
toV: 22,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680888715364,
|
||||
end_ts: 1680888718726,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
rename: {
|
||||
pathname: 'newfolder1/newfolder2/newfile3.tex',
|
||||
newPathname: 'newfile3.tex',
|
||||
},
|
||||
atV: 21,
|
||||
},
|
||||
{
|
||||
rename: {
|
||||
pathname: 'newfolder1/newfile2.tex',
|
||||
newPathname: 'newfolder1/newfolder2/newfile2.tex',
|
||||
},
|
||||
atV: 20,
|
||||
},
|
||||
{
|
||||
rename: {
|
||||
pathname: 'newfolder1/newfile5.tex',
|
||||
newPathname: 'newfile5.tex',
|
||||
},
|
||||
atV: 19,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fromV: 16,
|
||||
toV: 19,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680888705042,
|
||||
end_ts: 1680888712662,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [
|
||||
'main.tex',
|
||||
'newfolder1/newfile2.tex',
|
||||
'newfolder1/newfile5.tex',
|
||||
],
|
||||
project_ops: [],
|
||||
},
|
||||
{
|
||||
fromV: 0,
|
||||
toV: 16,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680888456499,
|
||||
end_ts: 1680888640774,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
add: {
|
||||
pathname: 'newfolder1/newfile2.tex',
|
||||
},
|
||||
atV: 15,
|
||||
},
|
||||
{
|
||||
remove: {
|
||||
pathname: 'newfile2.tex',
|
||||
},
|
||||
atV: 14,
|
||||
},
|
||||
{
|
||||
rename: {
|
||||
pathname: 'newfolder1/frog.jpg',
|
||||
newPathname: 'frog.jpg',
|
||||
},
|
||||
atV: 13,
|
||||
},
|
||||
{
|
||||
rename: {
|
||||
pathname: 'newfolder1/newfile2.tex',
|
||||
newPathname: 'newfile2.tex',
|
||||
},
|
||||
atV: 12,
|
||||
},
|
||||
{
|
||||
rename: {
|
||||
pathname: 'newfile5.tex',
|
||||
newPathname: 'newfolder1/newfile5.tex',
|
||||
},
|
||||
atV: 11,
|
||||
},
|
||||
{
|
||||
rename: {
|
||||
pathname: 'newfile4.tex',
|
||||
newPathname: 'newfile5.tex',
|
||||
},
|
||||
atV: 10,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'newfile4.tex',
|
||||
},
|
||||
atV: 9,
|
||||
},
|
||||
{
|
||||
remove: {
|
||||
pathname: 'newfolder1/newfolder2/newfile1.tex',
|
||||
},
|
||||
atV: 8,
|
||||
},
|
||||
{
|
||||
rename: {
|
||||
pathname: 'frog.jpg',
|
||||
newPathname: 'newfolder1/frog.jpg',
|
||||
},
|
||||
atV: 7,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'newfolder1/newfolder2/newfile3.tex',
|
||||
},
|
||||
atV: 6,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'newfolder1/newfile2.tex',
|
||||
},
|
||||
atV: 5,
|
||||
},
|
||||
{
|
||||
rename: {
|
||||
pathname: 'newfolder1/newfile1.tex',
|
||||
newPathname: 'newfolder1/newfolder2/newfile1.tex',
|
||||
},
|
||||
atV: 4,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'newfolder1/newfile1.tex',
|
||||
},
|
||||
atV: 3,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'frog.jpg',
|
||||
},
|
||||
atV: 2,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'sample.bib',
|
||||
},
|
||||
atV: 1,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'main.tex',
|
||||
},
|
||||
atV: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const { pathname } = autoSelectFile(
|
||||
files,
|
||||
updates[0].toV,
|
||||
comparing,
|
||||
getUpdateForVersion(updates[0].toV, updates),
|
||||
null
|
||||
)
|
||||
|
||||
expect(pathname).to.equal('newfolder1/newfile10.tex')
|
||||
})
|
||||
|
||||
it('return file with `added` operation on highest `atV` value if no other operation is available on the latest `updates` entry', function () {
|
||||
const files: FileDiff[] = [
|
||||
{
|
||||
pathname: 'main.tex',
|
||||
operation: 'added',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'sample.bib',
|
||||
operation: 'added',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'frog.jpg',
|
||||
operation: 'added',
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
pathname: 'newfile1.tex',
|
||||
operation: 'added',
|
||||
editable: true,
|
||||
},
|
||||
]
|
||||
|
||||
const updates: LoadedUpdate[] = [
|
||||
{
|
||||
fromV: 0,
|
||||
toV: 4,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680861468999,
|
||||
end_ts: 1680861491861,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
add: {
|
||||
pathname: 'newfile1.tex',
|
||||
},
|
||||
atV: 3,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'frog.jpg',
|
||||
},
|
||||
atV: 2,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'sample.bib',
|
||||
},
|
||||
atV: 1,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'main.tex',
|
||||
},
|
||||
atV: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const { pathname } = autoSelectFile(
|
||||
files,
|
||||
updates[0].toV,
|
||||
comparing,
|
||||
getUpdateForVersion(updates[0].toV, updates),
|
||||
null
|
||||
)
|
||||
|
||||
expect(pathname).to.equal('newfile1.tex')
|
||||
})
|
||||
|
||||
it('return the last non-`removed` operation with the highest `atV` value', function () {
|
||||
const files: FileDiff[] = [
|
||||
{
|
||||
pathname: 'main.tex',
|
||||
operation: 'removed',
|
||||
deletedAtV: 6,
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'sample.bib',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'main2.tex',
|
||||
operation: 'added',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'main3.tex',
|
||||
operation: 'added',
|
||||
editable: true,
|
||||
},
|
||||
]
|
||||
|
||||
const updates: LoadedUpdate[] = [
|
||||
{
|
||||
fromV: 4,
|
||||
toV: 7,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680874742389,
|
||||
end_ts: 1680874755552,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
remove: {
|
||||
pathname: 'main.tex',
|
||||
},
|
||||
atV: 6,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'main3.tex',
|
||||
},
|
||||
atV: 5,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'main2.tex',
|
||||
},
|
||||
atV: 4,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fromV: 0,
|
||||
toV: 4,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680861975947,
|
||||
end_ts: 1680861988442,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
remove: {
|
||||
pathname: 'frog.jpg',
|
||||
},
|
||||
atV: 3,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'frog.jpg',
|
||||
},
|
||||
atV: 2,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'sample.bib',
|
||||
},
|
||||
atV: 1,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'main.tex',
|
||||
},
|
||||
atV: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const { pathname } = autoSelectFile(
|
||||
files,
|
||||
updates[0].toV,
|
||||
comparing,
|
||||
getUpdateForVersion(updates[0].toV, updates),
|
||||
null
|
||||
)
|
||||
|
||||
expect(pathname).to.equal('main3.tex')
|
||||
})
|
||||
|
||||
it('if `removed` is the last operation, and no other operation is available on the latest `updates` entry, with `main.tex` available as a file name somewhere in the file tree, return `main.tex`', function () {
|
||||
const files: FileDiff[] = [
|
||||
{
|
||||
pathname: 'main.tex',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'sample.bib',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'frog.jpg',
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
pathname: 'newfolder/maybewillbedeleted.tex',
|
||||
newPathname: 'newfolder2/maybewillbedeleted.tex',
|
||||
operation: 'removed',
|
||||
deletedAtV: 10,
|
||||
editable: true,
|
||||
},
|
||||
]
|
||||
|
||||
const updates: LoadedUpdate[] = [
|
||||
{
|
||||
fromV: 9,
|
||||
toV: 11,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680904414419,
|
||||
end_ts: 1680904417538,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
remove: {
|
||||
pathname: 'newfolder2/maybewillbedeleted.tex',
|
||||
},
|
||||
atV: 10,
|
||||
},
|
||||
{
|
||||
rename: {
|
||||
pathname: 'newfolder/maybewillbedeleted.tex',
|
||||
newPathname: 'newfolder2/maybewillbedeleted.tex',
|
||||
},
|
||||
atV: 9,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fromV: 8,
|
||||
toV: 9,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680904410333,
|
||||
end_ts: 1680904410333,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: ['newfolder/maybewillbedeleted.tex'],
|
||||
project_ops: [],
|
||||
},
|
||||
{
|
||||
fromV: 7,
|
||||
toV: 8,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680904407448,
|
||||
end_ts: 1680904407448,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
rename: {
|
||||
pathname: 'newfolder/tobedeleted.tex',
|
||||
newPathname: 'newfolder/maybewillbedeleted.tex',
|
||||
},
|
||||
atV: 7,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fromV: 6,
|
||||
toV: 7,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680904400839,
|
||||
end_ts: 1680904400839,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: ['newfolder/tobedeleted.tex'],
|
||||
project_ops: [],
|
||||
},
|
||||
{
|
||||
fromV: 5,
|
||||
toV: 6,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680904398544,
|
||||
end_ts: 1680904398544,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
rename: {
|
||||
pathname: 'tobedeleted.tex',
|
||||
newPathname: 'newfolder/tobedeleted.tex',
|
||||
},
|
||||
atV: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fromV: 4,
|
||||
toV: 5,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680904389891,
|
||||
end_ts: 1680904389891,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: ['tobedeleted.tex'],
|
||||
project_ops: [],
|
||||
},
|
||||
{
|
||||
fromV: 0,
|
||||
toV: 4,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680904363778,
|
||||
end_ts: 1680904385308,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
add: {
|
||||
pathname: 'tobedeleted.tex',
|
||||
},
|
||||
atV: 3,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'frog.jpg',
|
||||
},
|
||||
atV: 2,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'sample.bib',
|
||||
},
|
||||
atV: 1,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'main.tex',
|
||||
},
|
||||
atV: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const { pathname } = autoSelectFile(
|
||||
files,
|
||||
updates[0].toV,
|
||||
comparing,
|
||||
getUpdateForVersion(updates[0].toV, updates),
|
||||
null
|
||||
)
|
||||
|
||||
expect(pathname).to.equal('main.tex')
|
||||
})
|
||||
|
||||
it('if `removed` is the last operation, and no other operation is available on the latest `updates` entry, with `main.tex` is not available as a file name somewhere in the file tree, return any tex file based on ascending alphabetical order', function () {
|
||||
const files: FileDiff[] = [
|
||||
{
|
||||
pathname: 'certainly_not_main.tex',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'newfile.tex',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'file2.tex',
|
||||
editable: true,
|
||||
},
|
||||
]
|
||||
|
||||
const updates: LoadedUpdate[] = [
|
||||
{
|
||||
fromV: 7,
|
||||
toV: 8,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680905536168,
|
||||
end_ts: 1680905536168,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
remove: {
|
||||
pathname: 'newfolder/tobedeleted.txt',
|
||||
},
|
||||
atV: 7,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fromV: 6,
|
||||
toV: 7,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680905531816,
|
||||
end_ts: 1680905531816,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: ['newfolder/tobedeleted.txt'],
|
||||
project_ops: [],
|
||||
},
|
||||
{
|
||||
fromV: 0,
|
||||
toV: 6,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680905492130,
|
||||
end_ts: 1680905529186,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
rename: {
|
||||
pathname: 'tobedeleted.txt',
|
||||
newPathname: 'newfolder/tobedeleted.txt',
|
||||
},
|
||||
atV: 5,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'file2.tex',
|
||||
},
|
||||
atV: 4,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'newfile.tex',
|
||||
},
|
||||
atV: 3,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'tobedeleted.txt',
|
||||
},
|
||||
atV: 2,
|
||||
},
|
||||
{
|
||||
rename: {
|
||||
pathname: 'main.tex',
|
||||
newPathname: 'certainly_not_main.tex',
|
||||
},
|
||||
atV: 1,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'main.tex',
|
||||
},
|
||||
atV: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const { pathname } = autoSelectFile(
|
||||
files,
|
||||
updates[0].toV,
|
||||
comparing,
|
||||
getUpdateForVersion(updates[0].toV, updates),
|
||||
null
|
||||
)
|
||||
|
||||
expect(pathname).to.equal('certainly_not_main.tex')
|
||||
})
|
||||
|
||||
it('selects renamed file', function () {
|
||||
const files: FileDiff[] = [
|
||||
{
|
||||
pathname: 'main.tex',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'original.bib',
|
||||
newPathname: 'new.bib',
|
||||
operation: 'renamed',
|
||||
editable: true,
|
||||
},
|
||||
]
|
||||
|
||||
const updates: LoadedUpdate[] = [
|
||||
{
|
||||
fromV: 4,
|
||||
toV: 7,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680874742389,
|
||||
end_ts: 1680874755552,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
rename: {
|
||||
pathname: 'original.bib',
|
||||
newPathname: 'new.bib',
|
||||
},
|
||||
atV: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fromV: 0,
|
||||
toV: 4,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680861975947,
|
||||
end_ts: 1680861988442,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
add: {
|
||||
pathname: 'original.bib',
|
||||
},
|
||||
atV: 1,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'main.tex',
|
||||
},
|
||||
atV: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const pathname = fileFinalPathname(
|
||||
autoSelectFile(
|
||||
files,
|
||||
updates[0].toV,
|
||||
comparing,
|
||||
getUpdateForVersion(updates[0].toV, updates),
|
||||
null
|
||||
)
|
||||
)
|
||||
|
||||
expect(pathname).to.equal('new.bib')
|
||||
})
|
||||
|
||||
it('ignores binary file', function () {
|
||||
const files: FileDiff[] = [
|
||||
{
|
||||
pathname: 'frog.jpg',
|
||||
editable: false,
|
||||
operation: 'added',
|
||||
},
|
||||
{
|
||||
pathname: 'main.tex',
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
pathname: 'sample.bib',
|
||||
editable: true,
|
||||
},
|
||||
]
|
||||
|
||||
const updates: LoadedUpdate[] = [
|
||||
{
|
||||
fromV: 4,
|
||||
toV: 7,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680874742389,
|
||||
end_ts: 1680874755552,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
add: {
|
||||
pathname: 'frog.jpg',
|
||||
},
|
||||
atV: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fromV: 0,
|
||||
toV: 4,
|
||||
meta: {
|
||||
users: historyUsers,
|
||||
start_ts: 1680861975947,
|
||||
end_ts: 1680861988442,
|
||||
},
|
||||
labels: [],
|
||||
pathnames: [],
|
||||
project_ops: [
|
||||
{
|
||||
add: {
|
||||
pathname: 'sample.bib',
|
||||
},
|
||||
atV: 1,
|
||||
},
|
||||
{
|
||||
add: {
|
||||
pathname: 'main.tex',
|
||||
},
|
||||
atV: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const { pathname } = autoSelectFile(
|
||||
files,
|
||||
updates[0].toV,
|
||||
comparing,
|
||||
getUpdateForVersion(updates[0].toV, updates),
|
||||
null
|
||||
)
|
||||
|
||||
expect(pathname).to.equal('main.tex')
|
||||
})
|
||||
})
|
||||
})
|
@@ -0,0 +1,68 @@
|
||||
import { expect } from 'chai'
|
||||
import displayNameForUser from '@/features/history/utils/display-name-for-user'
|
||||
|
||||
describe('displayNameForUser', function () {
|
||||
const currentUsersId = 'user-a'
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-user', { id: currentUsersId })
|
||||
})
|
||||
|
||||
it("should return 'Anonymous' with no user", function () {
|
||||
return expect(displayNameForUser(null)).to.equal('Anonymous')
|
||||
})
|
||||
|
||||
it("should return 'you' when the user has the same id as the window", function () {
|
||||
return expect(
|
||||
displayNameForUser({
|
||||
id: currentUsersId,
|
||||
email: 'james.allen@overleaf.com',
|
||||
first_name: 'James',
|
||||
last_name: 'Allen',
|
||||
})
|
||||
).to.equal('you')
|
||||
})
|
||||
|
||||
it('should return the first_name and last_name when present', function () {
|
||||
return expect(
|
||||
displayNameForUser({
|
||||
id: currentUsersId + 1,
|
||||
email: 'james.allen@overleaf.com',
|
||||
first_name: 'James',
|
||||
last_name: 'Allen',
|
||||
})
|
||||
).to.equal('James Allen')
|
||||
})
|
||||
|
||||
it('should return only the first_name if no last_name', function () {
|
||||
return expect(
|
||||
displayNameForUser({
|
||||
id: currentUsersId + 1,
|
||||
email: 'james.allen@overleaf.com',
|
||||
first_name: 'James',
|
||||
last_name: '',
|
||||
})
|
||||
).to.equal('James')
|
||||
})
|
||||
|
||||
it('should return the email username if there are no names', function () {
|
||||
return expect(
|
||||
displayNameForUser({
|
||||
id: currentUsersId + 1,
|
||||
email: 'james.allen@overleaf.com',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
})
|
||||
).to.equal('james.allen')
|
||||
})
|
||||
|
||||
it("should return the '?' if it has nothing", function () {
|
||||
return expect(
|
||||
displayNameForUser({
|
||||
id: currentUsersId + 1,
|
||||
email: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
})
|
||||
).to.equal('?')
|
||||
})
|
||||
})
|
Reference in New Issue
Block a user