Merge pull request #31397 from overleaf/mj-old-editor-toolbar-teardown
[web] Tear down old editor toolbar GitOrigin-RevId: 8ba74abcc56e7bd476a9d6cae72f38486168c2ed
This commit is contained in:
committed by
Copybot
parent
4f284e15d5
commit
cc1dedca7d
-26
@@ -1,26 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
|
||||
import ChatToggleButton from '../../../../../frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button'
|
||||
|
||||
describe('<ChatToggleButton />', function () {
|
||||
const defaultProps = {
|
||||
chatIsOpen: false,
|
||||
unreadMessageCount: 0,
|
||||
onClick: () => {},
|
||||
}
|
||||
|
||||
it('displays the number of unread messages', function () {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
unreadMessageCount: 113,
|
||||
}
|
||||
render(<ChatToggleButton {...props} />)
|
||||
screen.getByText('113')
|
||||
})
|
||||
|
||||
it("doesn't display the unread messages badge when the number of unread messages is zero", function () {
|
||||
render(<ChatToggleButton {...defaultProps} />)
|
||||
expect(screen.queryByText('0')).to.not.exist
|
||||
})
|
||||
})
|
||||
-237
@@ -1,237 +0,0 @@
|
||||
import sinon from 'sinon'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import { expect } from 'chai'
|
||||
import { screen, waitFor } from '@testing-library/react'
|
||||
import LayoutDropdownButton from '../../../../../frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
import * as eventTracking from '@/infrastructure/event-tracking'
|
||||
import type { LayoutContextOwnStates } from '@/shared/context/layout-context'
|
||||
|
||||
describe('<LayoutDropdownButton />', function () {
|
||||
let openStub: sinon.SinonStub
|
||||
let sendMBSpy: sinon.SinonSpy
|
||||
|
||||
const defaultLayout: Partial<LayoutContextOwnStates> = {
|
||||
pdfLayout: 'flat',
|
||||
view: 'pdf',
|
||||
chatIsOpen: false,
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
openStub = sinon.stub(window, 'open')
|
||||
sendMBSpy = sinon.spy(eventTracking, 'sendMB')
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
openStub.restore()
|
||||
sendMBSpy.restore()
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('should mark current layout option as selected', async function () {
|
||||
// Selected is aria-label, visually we show a checkmark
|
||||
renderWithEditorContext(<LayoutDropdownButton />, {
|
||||
layoutContext: defaultLayout,
|
||||
})
|
||||
|
||||
screen.getByRole('button', { name: 'Layout' }).click()
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor & PDF',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
)
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'PDF only (hide editor)',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('true')
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor only (hide PDF)',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'PDF in separate tab',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
})
|
||||
|
||||
it('should not select any option in history view', async function () {
|
||||
// Selected is aria-label, visually we show a checkmark
|
||||
renderWithEditorContext(<LayoutDropdownButton />, {
|
||||
layoutContext: { ...defaultLayout, view: 'history' },
|
||||
})
|
||||
|
||||
screen.getByRole('button', { name: 'Layout' }).click()
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor & PDF',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
)
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'PDF only (hide editor)',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor only (hide PDF)',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'PDF in separate tab',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
})
|
||||
|
||||
it('should treat file and editor views the same way', async function () {
|
||||
// Selected is aria-label, visually we show a checkmark
|
||||
renderWithEditorContext(<LayoutDropdownButton />, {
|
||||
layoutContext: {
|
||||
pdfLayout: 'flat',
|
||||
view: 'file',
|
||||
chatIsOpen: false,
|
||||
},
|
||||
})
|
||||
|
||||
screen.getByRole('button', { name: 'Layout' }).click()
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor & PDF',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
)
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'PDF only (hide editor)',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor only (hide PDF)',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('true')
|
||||
|
||||
expect(
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'PDF in separate tab',
|
||||
})
|
||||
.getAttribute('aria-selected')
|
||||
).to.equal('false')
|
||||
})
|
||||
|
||||
describe('on detach', async function () {
|
||||
const originalBroadcastChannel = window.BroadcastChannel
|
||||
beforeEach(async function () {
|
||||
// @ts-expect-error
|
||||
window.BroadcastChannel = true // ensure that window.BroadcastChannel is truthy
|
||||
|
||||
renderWithEditorContext(<LayoutDropdownButton />, {
|
||||
layoutContext: { ...defaultLayout, view: 'editor' },
|
||||
})
|
||||
|
||||
screen.getByRole('button', { name: 'Layout' }).click()
|
||||
|
||||
await waitFor(() =>
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'PDF in separate tab',
|
||||
})
|
||||
.click()
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
window.BroadcastChannel = originalBroadcastChannel
|
||||
})
|
||||
|
||||
it('should show processing', async function () {
|
||||
await screen.findByText('Layout processing')
|
||||
})
|
||||
|
||||
it('should record event', function () {
|
||||
sinon.assert.calledWith(sendMBSpy, 'project-layout-detach')
|
||||
})
|
||||
})
|
||||
|
||||
describe('on layout change / reattach', async function () {
|
||||
beforeEach(async function () {
|
||||
window.metaAttributesCache.set('ol-detachRole', 'detacher')
|
||||
renderWithEditorContext(<LayoutDropdownButton />, {
|
||||
layoutContext: { ...defaultLayout, view: 'editor' },
|
||||
})
|
||||
|
||||
screen.getByRole('button', { name: 'Layout' }).click()
|
||||
|
||||
await waitFor(() =>
|
||||
screen
|
||||
.getByRole('menuitem', {
|
||||
name: 'Editor only (hide PDF)',
|
||||
})
|
||||
.click()
|
||||
)
|
||||
})
|
||||
|
||||
it('should not show processing', function () {
|
||||
const processingText = screen.queryByText('Layout processing')
|
||||
expect(processingText).to.not.exist
|
||||
})
|
||||
|
||||
it('should record events', function () {
|
||||
sinon.assert.calledWith(sendMBSpy, 'project-layout-reattach')
|
||||
sinon.assert.calledWith(sendMBSpy, 'project-layout-change', {
|
||||
layout: 'flat',
|
||||
view: 'editor',
|
||||
page: '/detacher',
|
||||
'editor-redesign': 'enabled',
|
||||
})
|
||||
})
|
||||
|
||||
it('should select new menu item', function () {
|
||||
screen.getByRole('menuitem', {
|
||||
name: 'Editor only (hide PDF)',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
+68
-70
@@ -1,115 +1,113 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { OnlineUsersWidget } from '@/features/editor-navigation-toolbar/components/online-users-widget'
|
||||
|
||||
import OnlineUsersWidget from '../../../../../frontend/js/features/editor-navigation-toolbar/components/online-users-widget'
|
||||
const names = ['alice', 'bob', 'charlie', 'dave', 'erin', 'frank', 'grace']
|
||||
|
||||
function makeUsers(count: number) {
|
||||
return Array.from({ length: count }, (_, index) => ({
|
||||
id: `user_${index + 1}`,
|
||||
user_id: `user_id_${index + 1}`,
|
||||
name: names[index % names.length],
|
||||
email: `${names[index % names.length]}_email`,
|
||||
}))
|
||||
}
|
||||
|
||||
describe('<OnlineUsersWidget />', function () {
|
||||
const defaultProps = {
|
||||
onlineUsers: [
|
||||
{
|
||||
id: 'test_user',
|
||||
user_id: 'test_user',
|
||||
name: 'test_user',
|
||||
email: 'test_email',
|
||||
},
|
||||
{
|
||||
id: 'another_test_user',
|
||||
user_id: 'another_test_user',
|
||||
name: 'another_test_user',
|
||||
email: 'another_test_email',
|
||||
},
|
||||
],
|
||||
goToUser: (async () => {}) as any,
|
||||
onlineUsers: makeUsers(2),
|
||||
goToUser: sinon.stub(),
|
||||
}
|
||||
|
||||
describe('with less than 4 users', function () {
|
||||
describe('with less than 5 users', function () {
|
||||
it('displays user initials', function () {
|
||||
render(<OnlineUsersWidget {...defaultProps} />)
|
||||
screen.getByText('t')
|
||||
screen.getByText('a')
|
||||
screen.getByText('b')
|
||||
})
|
||||
|
||||
it('displays user name in a tooltip', async function () {
|
||||
render(<OnlineUsersWidget {...defaultProps} />)
|
||||
const icon = screen.getByText('t')
|
||||
const icon = screen.getByText('a')
|
||||
fireEvent.mouseOver(icon)
|
||||
await screen.findByRole('tooltip', { name: 'test_user' })
|
||||
await screen.findByRole('tooltip', { name: 'alice' })
|
||||
})
|
||||
|
||||
it('calls "goToUser" when the user initial is clicked', function () {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
goToUser: sinon.stub(),
|
||||
}
|
||||
render(<OnlineUsersWidget {...props} />)
|
||||
render(<OnlineUsersWidget {...defaultProps} />)
|
||||
|
||||
const icon = screen.getByText('t')
|
||||
const icon = screen.getByText('a')
|
||||
fireEvent.click(icon)
|
||||
|
||||
expect(props.goToUser).to.be.calledWith({
|
||||
id: 'test_user',
|
||||
user_id: 'test_user',
|
||||
name: 'test_user',
|
||||
email: 'test_email',
|
||||
expect(defaultProps.goToUser).to.be.calledWith({
|
||||
id: 'user_1',
|
||||
user_id: 'user_id_1',
|
||||
name: 'alice',
|
||||
email: 'alice_email',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('with 4 users and more', function () {
|
||||
describe('with 5 users', function () {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
onlineUsers: defaultProps.onlineUsers.concat([
|
||||
{
|
||||
id: 'user_3',
|
||||
user_id: 'user_3',
|
||||
name: 'user_3',
|
||||
email: 'user_3',
|
||||
},
|
||||
{
|
||||
id: 'user_4',
|
||||
user_id: 'user_4',
|
||||
name: 'user_4',
|
||||
email: 'user_4',
|
||||
},
|
||||
]),
|
||||
onlineUsers: makeUsers(5),
|
||||
}
|
||||
|
||||
it('displays the count of users', function () {
|
||||
it('displays user initials', function () {
|
||||
render(<OnlineUsersWidget {...props} />)
|
||||
screen.getByText('4')
|
||||
screen.getByText('a')
|
||||
screen.getByText('b')
|
||||
screen.getByText('c')
|
||||
screen.getByText('d')
|
||||
screen.getByText('e')
|
||||
})
|
||||
})
|
||||
|
||||
describe('with more than 5 users', function () {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
onlineUsers: makeUsers(7),
|
||||
}
|
||||
|
||||
it('displays a maximum of 4 user initials and an overflow icon', function () {
|
||||
render(<OnlineUsersWidget {...props} />)
|
||||
screen.getByText('a')
|
||||
screen.getByText('b')
|
||||
screen.getByText('c')
|
||||
screen.getByText('d')
|
||||
screen.getByText('+3')
|
||||
})
|
||||
|
||||
it('displays user names on hover', function () {
|
||||
it('displays the remaining users in a dropdown when the overflow icon is clicked', async function () {
|
||||
render(<OnlineUsersWidget {...props} />)
|
||||
const overflowButton = screen.getByText('+3')
|
||||
fireEvent.click(overflowButton)
|
||||
|
||||
const toggleButton = screen.getByRole('button')
|
||||
fireEvent.click(toggleButton)
|
||||
|
||||
screen.getByText('test_user')
|
||||
screen.getByText('another_test_user')
|
||||
screen.getByText('user_3')
|
||||
screen.getByText('user_4')
|
||||
await screen.findByText('erin')
|
||||
await screen.findByText('frank')
|
||||
await screen.findByText('grace')
|
||||
})
|
||||
|
||||
it('calls "goToUser" when the user name is clicked', function () {
|
||||
const testProps = {
|
||||
...props,
|
||||
goToUser: sinon.stub(),
|
||||
it('calls "goToUser" when a user in the overflow dropdown is clicked', async function () {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
onlineUsers: makeUsers(7),
|
||||
}
|
||||
render(<OnlineUsersWidget {...testProps} />)
|
||||
|
||||
const toggleButton = screen.getByRole('button')
|
||||
fireEvent.click(toggleButton)
|
||||
render(<OnlineUsersWidget {...props} />)
|
||||
const overflowButton = screen.getByText('+3')
|
||||
fireEvent.click(overflowButton)
|
||||
|
||||
const icon = screen.getByText('user_3')
|
||||
fireEvent.click(icon)
|
||||
const frankButton = await screen.findByText('frank')
|
||||
fireEvent.click(frankButton)
|
||||
|
||||
expect(testProps.goToUser).to.be.calledWith({
|
||||
id: 'user_3',
|
||||
user_id: 'user_3',
|
||||
name: 'user_3',
|
||||
email: 'user_3',
|
||||
expect(props.goToUser).to.be.calledWith({
|
||||
id: 'user_6',
|
||||
user_id: 'user_id_6',
|
||||
name: 'frank',
|
||||
email: 'frank_email',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
|
||||
import ProjectNameEditableLabel from '../../../../../frontend/js/features/editor-navigation-toolbar/components/project-name-editable-label'
|
||||
|
||||
describe('<ProjectNameEditableLabel />', function () {
|
||||
const defaultProps = { projectName: 'test-project', onChange: () => {} }
|
||||
|
||||
it('displays the project name', function () {
|
||||
render(<ProjectNameEditableLabel {...defaultProps} />)
|
||||
screen.getByText('test-project')
|
||||
})
|
||||
|
||||
describe('when the name is editable', function () {
|
||||
const editableProps = { ...defaultProps, hasRenamePermissions: true }
|
||||
|
||||
it('displays an editable input when the edit button is clicked', function () {
|
||||
render(<ProjectNameEditableLabel {...editableProps} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
screen.getByRole('textbox')
|
||||
})
|
||||
|
||||
it('displays an editable input when the project name is double clicked', function () {
|
||||
render(<ProjectNameEditableLabel {...editableProps} />)
|
||||
fireEvent.doubleClick(screen.getByText('test-project'))
|
||||
screen.getByRole('textbox')
|
||||
})
|
||||
|
||||
it('calls "onChange" when the project name is updated', function () {
|
||||
const props = {
|
||||
...editableProps,
|
||||
onChange: sinon.stub(),
|
||||
}
|
||||
render(<ProjectNameEditableLabel {...props} />)
|
||||
|
||||
fireEvent.doubleClick(screen.getByText('test-project'))
|
||||
const input = screen.getByRole('textbox')
|
||||
|
||||
fireEvent.change(input, { target: { value: 'new project name' } })
|
||||
fireEvent.keyDown(input, { key: 'Enter' })
|
||||
|
||||
expect(props.onChange).to.be.calledWith('new project name')
|
||||
})
|
||||
|
||||
it('calls "onChange" when the input loses focus', function () {
|
||||
const props = {
|
||||
...editableProps,
|
||||
onChange: sinon.stub(),
|
||||
}
|
||||
render(<ProjectNameEditableLabel {...props} />)
|
||||
|
||||
fireEvent.doubleClick(screen.getByText('test-project'))
|
||||
const input = screen.getByRole('textbox')
|
||||
|
||||
fireEvent.change(input, { target: { value: 'new project name' } })
|
||||
|
||||
fireEvent.blur(screen.getByRole('textbox'))
|
||||
|
||||
expect(props.onChange).to.be.calledWith('new project name')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the name is not editable', function () {
|
||||
const nonEditableProps = { hasRenamePermissions: false, ...defaultProps }
|
||||
|
||||
it('the edit button is not displayed', function () {
|
||||
render(<ProjectNameEditableLabel {...nonEditableProps} />)
|
||||
expect(screen.queryByRole('button')).to.not.exist
|
||||
})
|
||||
|
||||
it('does not display an editable input when the project name is double clicked', function () {
|
||||
render(<ProjectNameEditableLabel {...nonEditableProps} />)
|
||||
fireEvent.doubleClick(screen.getByText('test-project'))
|
||||
expect(screen.queryByRole('textbox')).to.not.exist
|
||||
})
|
||||
})
|
||||
})
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import { screen } from '@testing-library/react'
|
||||
|
||||
import ToolbarHeader, {
|
||||
type ToolbarHeaderProps,
|
||||
} from '../../../../../frontend/js/features/editor-navigation-toolbar/components/toolbar-header'
|
||||
import { renderWithEditorContext } from '../../../helpers/render-with-context'
|
||||
|
||||
describe('<ToolbarHeader />', function () {
|
||||
const defaultProps: ToolbarHeaderProps = {
|
||||
onShowLeftMenuClick: () => {},
|
||||
toggleChatOpen: () => {},
|
||||
toggleReviewPanelOpen: () => {},
|
||||
toggleHistoryOpen: () => {},
|
||||
unreadMessageCount: 0,
|
||||
onlineUsers: [],
|
||||
goToUser: (async () => {}) as any,
|
||||
projectName: 'test project',
|
||||
renameProject: () => {},
|
||||
openShareModal: () => {},
|
||||
hasPublishPermissions: true,
|
||||
chatVisible: true,
|
||||
trackChangesVisible: true,
|
||||
cobranding: undefined,
|
||||
isRestrictedTokenMember: false,
|
||||
hasRenamePermissions: true,
|
||||
historyIsOpen: false,
|
||||
chatIsOpen: false,
|
||||
reviewPanelOpen: false,
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
|
||||
})
|
||||
|
||||
describe('cobranding logo', function () {
|
||||
it('is not displayed by default', function () {
|
||||
renderWithEditorContext(<ToolbarHeader {...defaultProps} />)
|
||||
expect(screen.queryByRole('link', { name: 'variation' })).to.not.exist
|
||||
})
|
||||
|
||||
it('is displayed when cobranding data is available', function () {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
cobranding: {
|
||||
brandId: 12,
|
||||
brandVariationId: 12,
|
||||
brandVariationHomeUrl: 'http://cobranding',
|
||||
brandVariationName: 'variation',
|
||||
logoImgUrl: 'http://cobranding/logo',
|
||||
},
|
||||
}
|
||||
renderWithEditorContext(<ToolbarHeader {...props} />)
|
||||
screen.getByRole('link', { name: 'variation' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('track changes toggle button', function () {
|
||||
it('is displayed by default', function () {
|
||||
renderWithEditorContext(<ToolbarHeader {...defaultProps} />)
|
||||
screen.getByText('Review')
|
||||
})
|
||||
|
||||
it('is not displayed when "trackChangesVisible" prop is set to false', function () {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
trackChangesVisible: false,
|
||||
}
|
||||
renderWithEditorContext(<ToolbarHeader {...props} />)
|
||||
expect(screen.queryByText('Review')).to.not.exist
|
||||
})
|
||||
})
|
||||
|
||||
describe('History toggle button', function () {
|
||||
it('is displayed by default', function () {
|
||||
renderWithEditorContext(<ToolbarHeader {...defaultProps} />)
|
||||
screen.getByText('History')
|
||||
})
|
||||
|
||||
it('is not displayed when "isRestrictedTokenMember" prop is set to true', function () {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
isRestrictedTokenMember: true,
|
||||
}
|
||||
renderWithEditorContext(<ToolbarHeader {...props} />)
|
||||
expect(screen.queryByText('History')).to.not.exist
|
||||
})
|
||||
})
|
||||
|
||||
describe('Chat toggle button', function () {
|
||||
it('is displayed by default', function () {
|
||||
renderWithEditorContext(<ToolbarHeader {...defaultProps} />)
|
||||
screen.getByText('Chat')
|
||||
})
|
||||
|
||||
it('is not displayed when "chatVisible" prop is set to false', function () {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
chatVisible: false,
|
||||
}
|
||||
renderWithEditorContext(<ToolbarHeader {...props} />)
|
||||
expect(screen.queryByText('Chat')).to.not.exist
|
||||
})
|
||||
})
|
||||
|
||||
describe('Publish button', function () {
|
||||
it('is displayed by default', function () {
|
||||
renderWithEditorContext(<ToolbarHeader {...defaultProps} />)
|
||||
screen.getByText('Submit')
|
||||
})
|
||||
|
||||
it('is not displayed for users with no publish permissions', function () {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
hasPublishPermissions: false,
|
||||
}
|
||||
renderWithEditorContext(<ToolbarHeader {...props} />)
|
||||
expect(screen.queryByText('Submit')).to.not.exist
|
||||
})
|
||||
})
|
||||
})
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,248 @@
|
||||
import { Toolbar } from '@/features/ide-react/components/toolbar/toolbar'
|
||||
import {
|
||||
EditorProviders,
|
||||
makeEditorProvider,
|
||||
} from '../../../helpers/editor-providers'
|
||||
import { Cobranding } from '@ol-types/cobranding'
|
||||
import partnerLogoUrl from './cobranding-logo.png'
|
||||
|
||||
describe('<Toolbar />', function () {
|
||||
describe('cobranding', function () {
|
||||
beforeEach(function () {
|
||||
const cobranding: Cobranding = {
|
||||
logoImgUrl: partnerLogoUrl,
|
||||
brandVariationName: 'brand variation name',
|
||||
brandVariationId: 1000,
|
||||
brandId: 2000,
|
||||
brandVariationHomeUrl: 'https://brand.example.com',
|
||||
publishGuideHtml: 'string',
|
||||
partner: 'partner name',
|
||||
brandedMenu: true,
|
||||
submitBtnHtml: 'submit to\n partner',
|
||||
submitBtnHtmlNoBreaks: 'submit to partner',
|
||||
}
|
||||
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
providers={{ EditorProvider: makeEditorProvider({ cobranding }) }}
|
||||
>
|
||||
<Toolbar />
|
||||
</EditorProviders>
|
||||
)
|
||||
})
|
||||
|
||||
it('displays the cobranding logo', function () {
|
||||
cy.get(`img[src="${partnerLogoUrl}"]`).should('be.visible')
|
||||
})
|
||||
|
||||
it('shows the branded submit button', function () {
|
||||
cy.findByRole('button', { name: 'submit to partner' }).should(
|
||||
'be.visible'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('no cobranding', function () {
|
||||
beforeEach(function () {
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
providers={{
|
||||
EditorProvider: makeEditorProvider({ cobranding: undefined }),
|
||||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
</EditorProviders>
|
||||
)
|
||||
})
|
||||
|
||||
it('does not display a cobranding logo', function () {
|
||||
cy.get('.ide-redesign-toolbar-cobranding-logo').should('not.exist')
|
||||
})
|
||||
|
||||
it('does not show a submit button', function () {
|
||||
cy.findByRole('button', { name: 'Submit' }).should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
it('clicking the logo takes you to the home page', function () {
|
||||
cy.mount(
|
||||
<EditorProviders projectName="My title">
|
||||
<Toolbar />
|
||||
</EditorProviders>
|
||||
)
|
||||
cy.findByLabelText('Overleaf logo')
|
||||
// We can't click the link in component tests, so just look at the
|
||||
// parent directly
|
||||
.parent()
|
||||
.should('be.visible')
|
||||
.should('have.attr', 'href', '/project')
|
||||
})
|
||||
|
||||
describe('project title menu', function () {
|
||||
beforeEach(function () {
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
projectName="My title"
|
||||
providers={{
|
||||
EditorProvider: makeEditorProvider({
|
||||
renameProject: cy.stub().as('rename-project'),
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
</EditorProviders>
|
||||
)
|
||||
})
|
||||
|
||||
it('displays the project title dropdown', function () {
|
||||
cy.findByRole('button', { name: 'Project title options' })
|
||||
.should('be.visible')
|
||||
.should('contain.text', 'My title')
|
||||
.click()
|
||||
|
||||
cy.findByRole('menu')
|
||||
.should('exist')
|
||||
.within(() => {
|
||||
cy.findByRole('menuitem', { name: 'Download as PDF' }).should('exist')
|
||||
cy.findByRole('menuitem', {
|
||||
name: 'Download as source (.zip)',
|
||||
}).should('exist')
|
||||
cy.findByRole('menuitem', { name: 'Make a copy' }).should('exist')
|
||||
cy.findByRole('menuitem', { name: 'Rename' }).should('exist')
|
||||
})
|
||||
})
|
||||
|
||||
it('allows the project to be renamed', function () {
|
||||
cy.findByRole('button', { name: 'Project title options' }).click()
|
||||
cy.findByRole('menuitem', { name: 'Rename' }).click()
|
||||
cy.findByRole('textbox')
|
||||
.should('be.visible')
|
||||
.should('have.value', 'My title')
|
||||
.type('New title{enter}')
|
||||
cy.get('@rename-project').should('have.been.calledWith', 'New title')
|
||||
})
|
||||
|
||||
it('should show modal when copying project', function () {
|
||||
cy.findByRole('button', { name: 'Project title options' }).click()
|
||||
cy.findByRole('menuitem', { name: 'Make a copy' }).click()
|
||||
cy.findByRole('dialog')
|
||||
.should('be.visible')
|
||||
.should('contain.text', 'Copy project')
|
||||
})
|
||||
|
||||
it('should show modal when pressing submit', function () {
|
||||
cy.findByRole('button', { name: 'Project title options' }).click()
|
||||
cy.findByRole('menuitem', { name: 'Submit' }).click()
|
||||
cy.findByRole('dialog')
|
||||
.should('be.visible')
|
||||
.should('contain.text', 'Submit')
|
||||
})
|
||||
})
|
||||
|
||||
describe('history toggle', function () {
|
||||
it('Should show history button', function () {
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
projectName="My title"
|
||||
providers={{
|
||||
EditorProvider: makeEditorProvider({
|
||||
isRestrictedTokenMember: false,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
</EditorProviders>
|
||||
)
|
||||
cy.findByRole('button', { name: 'History' }).should('be.visible').click()
|
||||
})
|
||||
|
||||
it('Should not show history button to restricted token members', function () {
|
||||
cy.mount(
|
||||
<EditorProviders
|
||||
projectName="My title"
|
||||
providers={{
|
||||
EditorProvider: makeEditorProvider({
|
||||
isRestrictedTokenMember: true,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
</EditorProviders>
|
||||
)
|
||||
cy.findByRole('button', { name: 'History' }).should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
it('should show share button', function () {
|
||||
cy.mount(
|
||||
<EditorProviders projectName="My title">
|
||||
<Toolbar />
|
||||
</EditorProviders>
|
||||
)
|
||||
cy.findByRole('button', { name: 'Share' }).should('be.visible').click()
|
||||
cy.findByRole('dialog')
|
||||
.should('be.visible')
|
||||
.should('contain.text', 'Share Project')
|
||||
})
|
||||
|
||||
it('should show layout button', function () {
|
||||
cy.mount(
|
||||
<EditorProviders projectName="My title">
|
||||
<Toolbar />
|
||||
</EditorProviders>
|
||||
)
|
||||
cy.findByRole('button', { name: 'Layout options' })
|
||||
.should('be.visible')
|
||||
.click()
|
||||
cy.findByRole('menu')
|
||||
.should('exist')
|
||||
.within(() => {
|
||||
cy.findByRole('menuitem', { name: 'Split view' }).should('exist')
|
||||
cy.findByRole('menuitem', { name: 'Editor only' }).should('exist')
|
||||
cy.findByRole('menuitem', { name: 'PDF only' }).should('exist')
|
||||
cy.findByRole('menuitem', { name: 'Open PDF in separate tab' }).should(
|
||||
'exist'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('for non-owner', function () {
|
||||
it('should disable rename option', function () {
|
||||
cy.mount(
|
||||
<EditorProviders permissionsLevel="readOnly">
|
||||
<Toolbar />
|
||||
</EditorProviders>
|
||||
)
|
||||
cy.findByRole('button', { name: 'Project title options' }).click()
|
||||
// FIXME: This should really be a button rather than a link
|
||||
cy.findByRole('menuitem', { name: 'Rename' }).should(
|
||||
'have.class',
|
||||
'disabled'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('menu bar', function () {
|
||||
beforeEach(function () {
|
||||
cy.mount(
|
||||
<EditorProviders>
|
||||
<Toolbar />
|
||||
</EditorProviders>
|
||||
)
|
||||
})
|
||||
|
||||
it('should have a menu bar role', function () {
|
||||
cy.findByRole('menubar').should('be.visible')
|
||||
})
|
||||
|
||||
it('should show file, view & help', function () {
|
||||
cy.findByRole('menubar').within(() => {
|
||||
cy.findByRole('button', { name: 'File' }).should('be.visible')
|
||||
cy.findByRole('button', { name: 'View' }).should('be.visible')
|
||||
cy.findByRole('button', { name: 'Help' }).should('be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: Test all the dynamic items
|
||||
})
|
||||
})
|
||||
@@ -54,6 +54,7 @@ import { UserSettings } from '@ol-types/user-settings'
|
||||
import { DetachCompileContext } from '@/shared/context/detach-compile-context'
|
||||
import { type CompileContext } from '@/shared/context/local-compile-context'
|
||||
import { EditorContext } from '@/shared/context/editor-context'
|
||||
import { Cobranding } from '@ol-types/cobranding'
|
||||
|
||||
// these constants can be imported in tests instead of
|
||||
// using magic strings
|
||||
@@ -267,11 +268,21 @@ export function EditorProviders({
|
||||
)
|
||||
}
|
||||
|
||||
export function makeEditorProvider({ isProjectOwner = true } = {}) {
|
||||
export function makeEditorProvider({
|
||||
isProjectOwner = true,
|
||||
cobranding = undefined,
|
||||
renameProject = () => {},
|
||||
isRestrictedTokenMember,
|
||||
}: {
|
||||
isProjectOwner?: boolean
|
||||
cobranding?: Cobranding
|
||||
renameProject?: () => void
|
||||
isRestrictedTokenMember?: boolean
|
||||
} = {}) {
|
||||
const EditorProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
const value = {
|
||||
isProjectOwner,
|
||||
renameProject: () => {},
|
||||
renameProject,
|
||||
isPendingEditor: false,
|
||||
deactivateTutorial: () => {},
|
||||
inactiveTutorials: [],
|
||||
@@ -285,6 +296,8 @@ export function makeEditorProvider({ isProjectOwner = true } = {}) {
|
||||
setWritefullInstance: () => {},
|
||||
showUpgradeModal: false,
|
||||
setShowUpgradeModal: () => {},
|
||||
cobranding,
|
||||
isRestrictedTokenMember,
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user