editor-left-menu.spec.tsx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. import EditorLeftMenu from '../../../../frontend/js/features/editor-left-menu/components/editor-left-menu'
  2. import {
  3. ImageName,
  4. OverallThemeMeta,
  5. SpellCheckLanguage,
  6. } from '../../../../types/project-settings'
  7. import {
  8. EditorProviders,
  9. makeProjectProvider,
  10. } from '../../helpers/editor-providers'
  11. import { mockScope } from './scope'
  12. import { Folder } from '../../../../types/folder'
  13. import { docsInFolder } from '@/features/file-tree/util/docs-in-folder'
  14. import getMeta from '@/utils/meta'
  15. import { mockProject } from '../../features/source-editor/helpers/mock-project'
  16. import { UserId } from '../../../../types/user'
  17. describe('<EditorLeftMenu />', function () {
  18. beforeEach(function () {
  19. cy.viewport(800, 800)
  20. cy.interceptCompile()
  21. })
  22. describe('for non-anonymous users', function () {
  23. const overallThemes: OverallThemeMeta[] = [
  24. {
  25. name: 'Overall Theme 1',
  26. val: '',
  27. path: 'https://overleaf.com/overalltheme-1.css',
  28. },
  29. {
  30. name: 'Overall Theme 2',
  31. val: 'light-',
  32. path: 'https://overleaf.com/overalltheme-2.css',
  33. },
  34. ]
  35. const ImageNames: ImageName[] = [
  36. {
  37. imageDesc: 'Image 1',
  38. imageName: 'img-1',
  39. allowed: true,
  40. },
  41. {
  42. imageDesc: 'Image 2',
  43. imageName: 'img-2',
  44. allowed: true,
  45. },
  46. ]
  47. beforeEach(function () {
  48. window.metaAttributesCache.set('ol-overallThemes', overallThemes)
  49. window.metaAttributesCache.set('ol-imageNames', ImageNames)
  50. window.metaAttributesCache.set('ol-anonymous', false)
  51. window.metaAttributesCache.set('ol-gitBridgeEnabled', true)
  52. window.metaAttributesCache.set('ol-showSupport', true)
  53. Object.assign(getMeta('ol-ExposedSettings'), { ieeeBrandId: 123 })
  54. window.metaAttributesCache.set('ol-user', {
  55. email: 'sherlock@holmes.co.uk',
  56. first_name: 'Sherlock',
  57. last_name: 'Holmes',
  58. })
  59. })
  60. it('render full menu', function () {
  61. const scope = mockScope()
  62. const project = mockProject()
  63. cy.mount(
  64. <EditorProviders
  65. scope={scope}
  66. layoutContext={{ leftMenuShown: true }}
  67. providers={{ ProjectProvider: makeProjectProvider(project) }}
  68. >
  69. <EditorLeftMenu />
  70. </EditorProviders>
  71. )
  72. // Download Menu
  73. cy.findByRole('heading', { name: 'Download' })
  74. cy.findByRole('link', { name: 'Source' })
  75. cy.findByRole('link', { name: 'PDF' })
  76. // Actions Menu
  77. cy.findByRole('heading', { name: 'Actions' })
  78. cy.findByRole('button', { name: 'Copy project' })
  79. cy.findByRole('button', { name: 'Word Count' })
  80. // Sync Menu
  81. cy.findByRole('heading', { name: 'Sync' })
  82. cy.findByRole('button', { name: 'Dropbox' })
  83. cy.findByRole('button', { name: 'Git' })
  84. cy.findByRole('button', { name: 'GitHub' })
  85. // Settings Menu
  86. cy.findByRole('heading', { name: 'Settings' })
  87. cy.findByLabelText('Compiler')
  88. cy.findByLabelText('TeX Live version')
  89. cy.findByLabelText('Main document')
  90. cy.findByLabelText('Spell check')
  91. cy.findByLabelText('Auto-complete')
  92. cy.findByLabelText('Auto-close brackets')
  93. cy.findByLabelText('Code check')
  94. cy.findByLabelText('Editor theme')
  95. cy.findByLabelText('Overall theme')
  96. cy.findByLabelText('Keybindings')
  97. cy.findByLabelText('Font Size')
  98. cy.findByLabelText('Font Family')
  99. cy.findByLabelText('Line Height')
  100. cy.findByLabelText('PDF Viewer')
  101. // Help Menu
  102. cy.findByRole('heading', { name: 'Help' })
  103. cy.findByRole('button', { name: 'Show Hotkeys' })
  104. cy.findByRole('link', { name: 'Documentation' })
  105. cy.findByRole('button', { name: 'Contact us' })
  106. })
  107. describe('download menu', function () {
  108. it('have a correct source & pdf download url', function () {
  109. const scope = mockScope()
  110. cy.mount(
  111. <EditorProviders
  112. scope={scope}
  113. layoutContext={{ leftMenuShown: true }}
  114. >
  115. <EditorLeftMenu />
  116. </EditorProviders>
  117. )
  118. cy.findByRole('link', { name: 'Source' }).should(
  119. 'have.attr',
  120. 'href',
  121. '/project/project123/download/zip'
  122. )
  123. cy.findByRole('link', { name: 'PDF' })
  124. .should('have.attr', 'href')
  125. .and('match', /\/download\/project\/project123\/build/)
  126. })
  127. })
  128. describe('actions menu', function () {
  129. it('shows copy project modal correctly', function () {
  130. cy.intercept('POST', '/project/*/clone', {
  131. body: {
  132. project_id: 'new_project_id',
  133. },
  134. })
  135. const scope = mockScope()
  136. cy.mount(
  137. <EditorProviders
  138. scope={scope}
  139. layoutContext={{ leftMenuShown: true }}
  140. >
  141. <EditorLeftMenu />
  142. </EditorProviders>
  143. )
  144. cy.findByRole('button', { name: 'Copy project' }).click()
  145. cy.findByRole('heading', { name: 'Copy project' })
  146. // try closing & re-opening the modal with different methods
  147. cy.findByRole('button', { name: 'Close dialog' }).click()
  148. cy.findByRole('button', { name: 'Copy project' }).click()
  149. cy.findByRole('button', { name: 'Cancel' }).click()
  150. cy.findByRole('button', { name: 'Copy project' }).click()
  151. cy.findByLabelText(/New name/i).focus()
  152. cy.findByLabelText(/New name/i).clear()
  153. cy.findByLabelText(/New name/i).type('Project Renamed')
  154. cy.get('#clone-project-form-name[value="Project Renamed"')
  155. })
  156. it('shows word count modal correctly', function () {
  157. cy.intercept('GET', '/project/*/wordcount*', {
  158. texcount: {
  159. encode: 'ascii',
  160. textWords: 781,
  161. headWords: 66,
  162. outside: 11,
  163. headers: 41,
  164. elements: 2,
  165. mathInline: 6,
  166. mathDisplay: 1,
  167. errors: 0,
  168. },
  169. }).as('wordCount')
  170. const scope = mockScope()
  171. cy.mount(
  172. <EditorProviders
  173. scope={scope}
  174. layoutContext={{ leftMenuShown: true }}
  175. >
  176. <EditorLeftMenu />
  177. </EditorProviders>
  178. )
  179. cy.findByRole('button', { name: 'Word Count' }).click()
  180. cy.wait('@wordCount')
  181. cy.findByText('Total Words:')
  182. cy.findByText('781')
  183. cy.findByText('Headers:')
  184. cy.findByText('41')
  185. cy.findByText('Math Inline:')
  186. cy.findByText('6')
  187. cy.findByText('Math Display:')
  188. cy.findByText('1')
  189. })
  190. })
  191. describe('sync menu', function () {
  192. it('shows dropbox modal correctly', function () {
  193. cy.intercept('GET', '/dropbox/status', {
  194. registered: true,
  195. })
  196. const scope = mockScope({
  197. user: {
  198. features: {
  199. dropbox: false,
  200. },
  201. },
  202. })
  203. cy.mount(
  204. <EditorProviders
  205. scope={scope}
  206. layoutContext={{ leftMenuShown: true }}
  207. projectOwner={{
  208. _id: '123' as UserId,
  209. email: 'owner@example.com',
  210. first_name: 'Test',
  211. last_name: 'Owner',
  212. privileges: 'owner',
  213. signUpDate: new Date('2025-07-07').toISOString(),
  214. }}
  215. >
  216. <EditorLeftMenu />
  217. </EditorProviders>
  218. )
  219. cy.findByRole('button', { name: 'Dropbox' }).click()
  220. cy.findByText('Dropbox Sync')
  221. })
  222. it('shows git modal correctly', function () {
  223. const scope = mockScope()
  224. cy.mount(
  225. <EditorProviders
  226. scope={scope}
  227. layoutContext={{ leftMenuShown: true }}
  228. projectOwner={{
  229. _id: '123' as UserId,
  230. email: 'owner@example.com',
  231. first_name: 'Test',
  232. last_name: 'Owner',
  233. privileges: 'owner',
  234. signUpDate: new Date('2025-07-07').toISOString(),
  235. }}
  236. projectFeatures={
  237. {
  238. gitBridge: true,
  239. } as any
  240. }
  241. >
  242. <EditorLeftMenu />
  243. </EditorProviders>
  244. )
  245. cy.findByRole('button', { name: 'Git' }).click()
  246. cy.findByText('Clone with Git')
  247. cy.findByText(/clone your project by using the link below/)
  248. })
  249. it('shows git modal paywall correctly', function () {
  250. const scope = mockScope()
  251. cy.mount(
  252. <EditorProviders
  253. scope={scope}
  254. layoutContext={{ leftMenuShown: true }}
  255. projectOwner={{
  256. _id: '123' as UserId,
  257. email: 'owner@example.com',
  258. first_name: 'Test',
  259. last_name: 'Owner',
  260. privileges: 'owner',
  261. signUpDate: new Date('2025-07-07').toISOString(),
  262. }}
  263. projectFeatures={
  264. {
  265. gitBridge: false,
  266. } as any
  267. }
  268. >
  269. <EditorLeftMenu />
  270. </EditorProviders>
  271. )
  272. cy.findByRole('button', { name: 'Git' }).click()
  273. cy.findByText('Collaborate online and offline, using your own workflow')
  274. })
  275. it('shows github modal correctly', function () {
  276. cy.intercept('GET', '/user/github-sync/status', {
  277. available: false,
  278. enabled: false,
  279. }).as('user-status')
  280. cy.intercept('GET', '/project/*/github-sync/status', {
  281. enabled: false,
  282. }).as('project-status')
  283. const scope = mockScope()
  284. cy.mount(
  285. <EditorProviders
  286. scope={scope}
  287. layoutContext={{ leftMenuShown: true }}
  288. >
  289. <EditorLeftMenu />
  290. </EditorProviders>
  291. )
  292. cy.wait('@compile')
  293. cy.findByRole('button', { name: 'GitHub' }).click()
  294. cy.findByText('GitHub Sync')
  295. cy.wait(['@user-status', '@project-status'])
  296. cy.findByText('Push to GitHub, pull to Overleaf')
  297. })
  298. it('hides the entire sync section when git bridge is disabled', function () {
  299. window.metaAttributesCache.set('ol-gitBridgeEnabled', false)
  300. cy.findByRole('button', { name: 'Dropbox' }).should('not.exist')
  301. cy.findByRole('button', { name: 'Git' }).should('not.exist')
  302. cy.findByRole('button', { name: 'GitHub' }).should('not.exist')
  303. })
  304. })
  305. describe('settings menu', function () {
  306. it('shows compiler menu correctly', function () {
  307. const scope = mockScope()
  308. cy.mount(
  309. <EditorProviders
  310. scope={scope}
  311. layoutContext={{ leftMenuShown: true }}
  312. >
  313. <EditorLeftMenu />
  314. </EditorProviders>
  315. )
  316. cy.get<HTMLOptionElement>('#settings-menu-compiler option').then(
  317. options => {
  318. const values = [...options].map(o => o.value)
  319. expect(values).to.deep.eq([
  320. 'pdflatex',
  321. 'latex',
  322. 'xelatex',
  323. 'lualatex',
  324. ])
  325. const texts = [...options].map(o => o.text)
  326. expect(texts).to.deep.eq([
  327. 'pdfLaTeX',
  328. 'LaTeX',
  329. 'XeLaTeX',
  330. 'LuaLaTeX',
  331. ])
  332. }
  333. )
  334. })
  335. it('shows texlive version menu correctly', function () {
  336. const scope = mockScope()
  337. cy.mount(
  338. <EditorProviders
  339. scope={scope}
  340. layoutContext={{ leftMenuShown: true }}
  341. >
  342. <EditorLeftMenu />
  343. </EditorProviders>
  344. )
  345. cy.get<HTMLOptionElement>('#settings-menu-imageName option').then(
  346. options => {
  347. const values = [...options].map(o => o.value)
  348. expect(values).to.deep.eq(['img-1', 'img-2'])
  349. const texts = [...options].map(o => o.text)
  350. expect(texts).to.deep.eq(['Image 1', 'Image 2'])
  351. }
  352. )
  353. })
  354. it('shows document menu correctly', function () {
  355. const rootFolder: Folder = {
  356. _id: 'root-folder-id',
  357. name: 'rootFolder',
  358. docs: [
  359. {
  360. _id: 'id1',
  361. name: 'main.tex',
  362. },
  363. {
  364. _id: 'id2',
  365. name: 'main2.tex',
  366. },
  367. ],
  368. fileRefs: [],
  369. folders: [],
  370. }
  371. const scope = mockScope()
  372. cy.mount(
  373. <EditorProviders
  374. layoutContext={{ leftMenuShown: true }}
  375. scope={scope}
  376. rootFolder={[rootFolder as any]}
  377. >
  378. <EditorLeftMenu />
  379. </EditorProviders>
  380. )
  381. const docs = docsInFolder(rootFolder)
  382. cy.get<HTMLOptionElement>('#settings-menu-rootDocId option').then(
  383. options => {
  384. const values = [...options].map(o => o.value)
  385. expect(values).to.deep.eq(docs.map(doc => doc.doc.id))
  386. const texts = [...options].map(o => o.text)
  387. expect(texts).to.deep.eq(docs.map(doc => doc.path))
  388. }
  389. )
  390. })
  391. it('shows spellcheck menu correctly', function () {
  392. const languages: SpellCheckLanguage[] = [
  393. {
  394. name: 'Lang 1',
  395. code: 'lang-1',
  396. dic: 'lang_1',
  397. },
  398. {
  399. name: 'Lang 2',
  400. code: 'lang-2',
  401. dic: 'lang_2',
  402. },
  403. ]
  404. window.metaAttributesCache.set('ol-languages', languages)
  405. const scope = mockScope()
  406. cy.mount(
  407. <EditorProviders
  408. scope={scope}
  409. layoutContext={{ leftMenuShown: true }}
  410. >
  411. <EditorLeftMenu />
  412. </EditorProviders>
  413. )
  414. cy.get<HTMLOptionElement>(
  415. '#settings-menu-spellCheckLanguage option'
  416. ).then(options => {
  417. const values = [...options].map(o => o.value)
  418. expect(values).to.deep.eq(['', 'lang-1', 'lang-2'])
  419. const texts = [...options].map(o => o.text)
  420. expect(texts).to.deep.eq(['Off', 'Lang 1', 'Lang 2'])
  421. })
  422. })
  423. it('shows dictionary modal correctly', function () {
  424. const scope = mockScope()
  425. cy.mount(
  426. <EditorProviders
  427. scope={scope}
  428. layoutContext={{ leftMenuShown: true }}
  429. >
  430. <EditorLeftMenu />
  431. </EditorProviders>
  432. )
  433. cy.get('label[for="dictionary-settings"] ~ button').click()
  434. cy.findByText('Edit Dictionary')
  435. cy.findByText('Your custom dictionary is empty.')
  436. })
  437. it('shows auto-complete menu correctly', function () {
  438. const scope = mockScope()
  439. cy.mount(
  440. <EditorProviders
  441. scope={scope}
  442. layoutContext={{ leftMenuShown: true }}
  443. >
  444. <EditorLeftMenu />
  445. </EditorProviders>
  446. )
  447. cy.get<HTMLOptionElement>('#settings-menu-autoComplete option').then(
  448. options => {
  449. const values = [...options].map(o => o.value)
  450. expect(values).to.deep.eq(['true', 'false'])
  451. const texts = [...options].map(o => o.text)
  452. expect(texts).to.deep.eq(['On', 'Off'])
  453. }
  454. )
  455. })
  456. it('shows auto-close brackets menu correctly', function () {
  457. const scope = mockScope()
  458. cy.mount(
  459. <EditorProviders
  460. scope={scope}
  461. layoutContext={{ leftMenuShown: true }}
  462. >
  463. <EditorLeftMenu />
  464. </EditorProviders>
  465. )
  466. cy.get<HTMLOptionElement>(
  467. '#settings-menu-autoPairDelimiters option'
  468. ).then(options => {
  469. const values = [...options].map(o => o.value)
  470. expect(values).to.deep.eq(['true', 'false'])
  471. const texts = [...options].map(o => o.text)
  472. expect(texts).to.deep.eq(['On', 'Off'])
  473. })
  474. })
  475. it('shows code check menu correctly', function () {
  476. const scope = mockScope()
  477. cy.mount(
  478. <EditorProviders
  479. scope={scope}
  480. layoutContext={{ leftMenuShown: true }}
  481. >
  482. <EditorLeftMenu />
  483. </EditorProviders>
  484. )
  485. cy.get<HTMLOptionElement>(
  486. '#settings-menu-syntaxValidation option'
  487. ).then(options => {
  488. const values = [...options].map(o => o.value)
  489. expect(values).to.deep.eq(['true', 'false'])
  490. const texts = [...options].map(o => o.text)
  491. expect(texts).to.deep.eq(['On', 'Off'])
  492. })
  493. })
  494. it('shows editor theme menu correctly', function () {
  495. const editorThemes = [
  496. { name: 'editortheme-1', dark: false },
  497. { name: 'editortheme-2', dark: false },
  498. { name: 'editortheme-3', dark: false },
  499. ]
  500. const legacyEditorThemes = [
  501. { name: 'legacytheme-1', dark: false },
  502. { name: 'legacytheme-2', dark: false },
  503. { name: 'legacytheme-3', dark: false },
  504. ]
  505. window.metaAttributesCache.set('ol-editorThemes', editorThemes)
  506. window.metaAttributesCache.set(
  507. 'ol-legacyEditorThemes',
  508. legacyEditorThemes
  509. )
  510. const scope = mockScope()
  511. cy.mount(
  512. <EditorProviders
  513. scope={scope}
  514. layoutContext={{ leftMenuShown: true }}
  515. >
  516. <EditorLeftMenu />
  517. </EditorProviders>
  518. )
  519. cy.get<HTMLOptionElement>('#settings-menu-editorTheme option').then(
  520. options => {
  521. const values = [...options].map(o => o.value)
  522. expect(values).to.deep.eq([
  523. 'editortheme-1',
  524. 'editortheme-2',
  525. 'editortheme-3',
  526. 'legacytheme-1',
  527. 'legacytheme-2',
  528. 'legacytheme-3',
  529. ])
  530. const texts = [...options].map(o => o.text)
  531. expect(texts).to.deep.eq([
  532. 'editortheme-1',
  533. 'editortheme-2',
  534. 'editortheme-3',
  535. 'legacytheme-1 (Legacy)',
  536. 'legacytheme-2 (Legacy)',
  537. 'legacytheme-3 (Legacy)',
  538. ])
  539. }
  540. )
  541. })
  542. it('shows overall theme menu correctly', function () {
  543. const scope = mockScope()
  544. cy.mount(
  545. <EditorProviders
  546. scope={scope}
  547. layoutContext={{ leftMenuShown: true }}
  548. >
  549. <EditorLeftMenu />
  550. </EditorProviders>
  551. )
  552. cy.get<HTMLOptionElement>('#settings-menu-overallTheme option').then(
  553. options => {
  554. const values = [...options].map(o => o.value)
  555. expect(values).to.deep.eq(['', 'light-'])
  556. const texts = [...options].map(o => o.text)
  557. expect(texts).to.deep.eq(['Overall Theme 1', 'Overall Theme 2'])
  558. }
  559. )
  560. })
  561. it('shows keybindings menu correctly', function () {
  562. const scope = mockScope()
  563. cy.mount(
  564. <EditorProviders
  565. scope={scope}
  566. layoutContext={{ leftMenuShown: true }}
  567. >
  568. <EditorLeftMenu />
  569. </EditorProviders>
  570. )
  571. cy.get<HTMLOptionElement>('#settings-menu-mode option').then(
  572. options => {
  573. const values = [...options].map(o => o.value)
  574. expect(values).to.deep.eq(['default', 'vim', 'emacs'])
  575. const texts = [...options].map(o => o.text)
  576. expect(texts).to.deep.eq(['None', 'Vim', 'Emacs'])
  577. }
  578. )
  579. })
  580. it('shows font size menu correctly', function () {
  581. const scope = mockScope()
  582. cy.mount(
  583. <EditorProviders
  584. scope={scope}
  585. layoutContext={{ leftMenuShown: true }}
  586. >
  587. <EditorLeftMenu />
  588. </EditorProviders>
  589. )
  590. cy.get<HTMLOptionElement>('#settings-menu-fontSize option').then(
  591. options => {
  592. const values = [...options].map(o => o.value)
  593. expect(values).to.deep.eq([
  594. '10',
  595. '11',
  596. '12',
  597. '13',
  598. '14',
  599. '16',
  600. '18',
  601. '20',
  602. '22',
  603. '24',
  604. ])
  605. const texts = [...options].map(o => o.text)
  606. expect(texts).to.deep.eq([
  607. '10px',
  608. '11px',
  609. '12px',
  610. '13px',
  611. '14px',
  612. '16px',
  613. '18px',
  614. '20px',
  615. '22px',
  616. '24px',
  617. ])
  618. }
  619. )
  620. })
  621. it('shows font family menu correctly', function () {
  622. const scope = mockScope()
  623. cy.mount(
  624. <EditorProviders
  625. scope={scope}
  626. layoutContext={{ leftMenuShown: true }}
  627. >
  628. <EditorLeftMenu />
  629. </EditorProviders>
  630. )
  631. cy.get<HTMLOptionElement>('#settings-menu-fontFamily option').then(
  632. options => {
  633. const values = [...options].map(o => o.value)
  634. expect(values).to.deep.eq(['monaco', 'lucida', 'opendyslexicmono'])
  635. const texts = [...options].map(o => o.text)
  636. expect(texts).to.deep.eq([
  637. 'Monaco / Menlo / Consolas',
  638. 'Lucida / Source Code Pro',
  639. 'OpenDyslexic Mono',
  640. ])
  641. }
  642. )
  643. })
  644. it('shows line height menu correctly', function () {
  645. const scope = mockScope()
  646. cy.mount(
  647. <EditorProviders
  648. scope={scope}
  649. layoutContext={{ leftMenuShown: true }}
  650. >
  651. <EditorLeftMenu />
  652. </EditorProviders>
  653. )
  654. cy.get<HTMLOptionElement>('#settings-menu-lineHeight option').then(
  655. options => {
  656. const values = [...options].map(o => o.value)
  657. expect(values).to.deep.eq(['compact', 'normal', 'wide'])
  658. const texts = [...options].map(o => o.text)
  659. expect(texts).to.deep.eq(['Compact', 'Normal', 'Wide'])
  660. }
  661. )
  662. })
  663. it('shows pdf viewer menu correctly', function () {
  664. const scope = mockScope()
  665. cy.mount(
  666. <EditorProviders
  667. scope={scope}
  668. layoutContext={{ leftMenuShown: true }}
  669. >
  670. <EditorLeftMenu />
  671. </EditorProviders>
  672. )
  673. cy.get<HTMLOptionElement>('#settings-menu-pdfViewer option').then(
  674. options => {
  675. const values = [...options].map(o => o.value)
  676. expect(values).to.deep.eq(['pdfjs', 'native'])
  677. const texts = [...options].map(o => o.text)
  678. expect(texts).to.deep.eq(['Overleaf', 'Browser'])
  679. }
  680. )
  681. })
  682. })
  683. describe('help menu', function () {
  684. it('shows hotkeys modal correctly', function () {
  685. const scope = mockScope()
  686. cy.mount(
  687. <EditorProviders
  688. scope={scope}
  689. layoutContext={{ leftMenuShown: true }}
  690. >
  691. <EditorLeftMenu />
  692. </EditorProviders>
  693. )
  694. cy.findByRole('button', { name: 'Show Hotkeys' }).click()
  695. cy.findByText('Hotkeys')
  696. })
  697. it('shows correct url for documentation', function () {
  698. const scope = mockScope()
  699. cy.mount(
  700. <EditorProviders
  701. scope={scope}
  702. layoutContext={{ leftMenuShown: true }}
  703. >
  704. <EditorLeftMenu />
  705. </EditorProviders>
  706. )
  707. cy.findByRole('link', { name: 'Documentation' }).should(
  708. 'have.attr',
  709. 'href',
  710. '/learn'
  711. )
  712. })
  713. it('shows correct contact us modal', function () {
  714. const scope = mockScope()
  715. cy.mount(
  716. <EditorProviders
  717. scope={scope}
  718. layoutContext={{ leftMenuShown: true }}
  719. >
  720. <EditorLeftMenu />
  721. </EditorProviders>
  722. )
  723. cy.findByRole('button', { name: 'Contact us' }).click()
  724. cy.findByText('Affected project URL (Optional)')
  725. })
  726. })
  727. })
  728. describe('for anonymous users', function () {
  729. it('render minimal menu', function () {
  730. const scope = mockScope()
  731. window.metaAttributesCache.set('ol-anonymous', true)
  732. Object.assign(getMeta('ol-ExposedSettings'), { ieeeBrandId: 123 })
  733. cy.mount(
  734. <EditorProviders scope={scope} layoutContext={{ leftMenuShown: true }}>
  735. <EditorLeftMenu />
  736. </EditorProviders>
  737. )
  738. // Download Menu
  739. cy.findByRole('heading', { name: 'Download' })
  740. cy.findByRole('link', { name: 'Source' })
  741. cy.findByRole('link', { name: 'PDF' })
  742. // Actions Menu
  743. cy.findByRole('heading', { name: 'Actions' }).should('not.exist')
  744. cy.findByRole('button', { name: 'Copy project' }).should('not.exist')
  745. cy.findByRole('button', { name: 'Word Count' }).should('not.exist')
  746. // Sync Menu
  747. cy.findByRole('heading', { name: 'Sync' }).should('not.exist')
  748. cy.findByRole('button', { name: 'Dropbox' }).should('not.exist')
  749. cy.findByRole('button', { name: 'Git' }).should('not.exist')
  750. cy.findByRole('button', { name: 'GitHub' }).should('not.exist')
  751. // Settings Menu
  752. cy.findByRole('heading', { name: 'Settings' }).should('not.exist')
  753. cy.findByLabelText('Compiler').should('not.exist')
  754. cy.findByLabelText('TeX Live version').should('not.exist')
  755. cy.findByLabelText('Main document').should('not.exist')
  756. cy.findByLabelText('Spell check').should('not.exist')
  757. cy.findByLabelText('Auto-complete').should('not.exist')
  758. cy.findByLabelText('Auto-close brackets').should('not.exist')
  759. cy.findByLabelText('Code check').should('not.exist')
  760. cy.findByLabelText('Editor theme').should('not.exist')
  761. cy.findByLabelText('Overall theme').should('not.exist')
  762. cy.findByLabelText('Keybindings').should('not.exist')
  763. cy.findByLabelText('Font Size').should('not.exist')
  764. cy.findByLabelText('Font Family').should('not.exist')
  765. cy.findByLabelText('Line Height').should('not.exist')
  766. cy.findByLabelText('PDF Viewer').should('not.exist')
  767. // Help Menu
  768. cy.findByRole('heading', { name: 'Help' })
  769. cy.findByRole('button', { name: 'Show Hotkeys' })
  770. cy.findByRole('button', { name: 'Documentation' }).should('not.exist')
  771. cy.findByRole('link', { name: 'Contact us' }).should('not.exist')
  772. })
  773. })
  774. })