editor-left-menu.spec.tsx 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898
  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 = ['editortheme-1', 'editortheme-2', 'editortheme-3']
  496. const legacyEditorThemes = [
  497. 'legacytheme-1',
  498. 'legacytheme-2',
  499. 'legacytheme-3',
  500. ]
  501. window.metaAttributesCache.set('ol-editorThemes', editorThemes)
  502. window.metaAttributesCache.set(
  503. 'ol-legacyEditorThemes',
  504. legacyEditorThemes
  505. )
  506. const scope = mockScope()
  507. cy.mount(
  508. <EditorProviders
  509. scope={scope}
  510. layoutContext={{ leftMenuShown: true }}
  511. >
  512. <EditorLeftMenu />
  513. </EditorProviders>
  514. )
  515. cy.get<HTMLOptionElement>('#settings-menu-editorTheme option').then(
  516. options => {
  517. const values = [...options].map(o => o.value)
  518. expect(values).to.deep.eq([
  519. 'editortheme-1',
  520. 'editortheme-2',
  521. 'editortheme-3',
  522. '-',
  523. 'legacytheme-1',
  524. 'legacytheme-2',
  525. 'legacytheme-3',
  526. ])
  527. const texts = [...options].map(o => o.text)
  528. expect(texts).to.deep.eq([
  529. 'editortheme-1',
  530. 'editortheme-2',
  531. 'editortheme-3',
  532. '—————————————————',
  533. 'legacytheme-1 (Legacy)',
  534. 'legacytheme-2 (Legacy)',
  535. 'legacytheme-3 (Legacy)',
  536. ])
  537. }
  538. )
  539. })
  540. it('shows overall theme menu correctly', function () {
  541. const scope = mockScope()
  542. cy.mount(
  543. <EditorProviders
  544. scope={scope}
  545. layoutContext={{ leftMenuShown: true }}
  546. >
  547. <EditorLeftMenu />
  548. </EditorProviders>
  549. )
  550. cy.get<HTMLOptionElement>('#settings-menu-overallTheme option').then(
  551. options => {
  552. const values = [...options].map(o => o.value)
  553. expect(values).to.deep.eq(['', 'light-'])
  554. const texts = [...options].map(o => o.text)
  555. expect(texts).to.deep.eq(['Overall Theme 1', 'Overall Theme 2'])
  556. }
  557. )
  558. })
  559. it('shows keybindings menu correctly', function () {
  560. const scope = mockScope()
  561. cy.mount(
  562. <EditorProviders
  563. scope={scope}
  564. layoutContext={{ leftMenuShown: true }}
  565. >
  566. <EditorLeftMenu />
  567. </EditorProviders>
  568. )
  569. cy.get<HTMLOptionElement>('#settings-menu-mode option').then(
  570. options => {
  571. const values = [...options].map(o => o.value)
  572. expect(values).to.deep.eq(['default', 'vim', 'emacs'])
  573. const texts = [...options].map(o => o.text)
  574. expect(texts).to.deep.eq(['None', 'Vim', 'Emacs'])
  575. }
  576. )
  577. })
  578. it('shows font size menu correctly', function () {
  579. const scope = mockScope()
  580. cy.mount(
  581. <EditorProviders
  582. scope={scope}
  583. layoutContext={{ leftMenuShown: true }}
  584. >
  585. <EditorLeftMenu />
  586. </EditorProviders>
  587. )
  588. cy.get<HTMLOptionElement>('#settings-menu-fontSize option').then(
  589. options => {
  590. const values = [...options].map(o => o.value)
  591. expect(values).to.deep.eq([
  592. '10',
  593. '11',
  594. '12',
  595. '13',
  596. '14',
  597. '16',
  598. '18',
  599. '20',
  600. '22',
  601. '24',
  602. ])
  603. const texts = [...options].map(o => o.text)
  604. expect(texts).to.deep.eq([
  605. '10px',
  606. '11px',
  607. '12px',
  608. '13px',
  609. '14px',
  610. '16px',
  611. '18px',
  612. '20px',
  613. '22px',
  614. '24px',
  615. ])
  616. }
  617. )
  618. })
  619. it('shows font family menu correctly', function () {
  620. const scope = mockScope()
  621. cy.mount(
  622. <EditorProviders
  623. scope={scope}
  624. layoutContext={{ leftMenuShown: true }}
  625. >
  626. <EditorLeftMenu />
  627. </EditorProviders>
  628. )
  629. cy.get<HTMLOptionElement>('#settings-menu-fontFamily option').then(
  630. options => {
  631. const values = [...options].map(o => o.value)
  632. expect(values).to.deep.eq(['monaco', 'lucida', 'opendyslexicmono'])
  633. const texts = [...options].map(o => o.text)
  634. expect(texts).to.deep.eq([
  635. 'Monaco / Menlo / Consolas',
  636. 'Lucida / Source Code Pro',
  637. 'OpenDyslexic Mono',
  638. ])
  639. }
  640. )
  641. })
  642. it('shows line height menu correctly', function () {
  643. const scope = mockScope()
  644. cy.mount(
  645. <EditorProviders
  646. scope={scope}
  647. layoutContext={{ leftMenuShown: true }}
  648. >
  649. <EditorLeftMenu />
  650. </EditorProviders>
  651. )
  652. cy.get<HTMLOptionElement>('#settings-menu-lineHeight option').then(
  653. options => {
  654. const values = [...options].map(o => o.value)
  655. expect(values).to.deep.eq(['compact', 'normal', 'wide'])
  656. const texts = [...options].map(o => o.text)
  657. expect(texts).to.deep.eq(['Compact', 'Normal', 'Wide'])
  658. }
  659. )
  660. })
  661. it('shows pdf viewer menu correctly', function () {
  662. const scope = mockScope()
  663. cy.mount(
  664. <EditorProviders
  665. scope={scope}
  666. layoutContext={{ leftMenuShown: true }}
  667. >
  668. <EditorLeftMenu />
  669. </EditorProviders>
  670. )
  671. cy.get<HTMLOptionElement>('#settings-menu-pdfViewer option').then(
  672. options => {
  673. const values = [...options].map(o => o.value)
  674. expect(values).to.deep.eq(['pdfjs', 'native'])
  675. const texts = [...options].map(o => o.text)
  676. expect(texts).to.deep.eq(['Overleaf', 'Browser'])
  677. }
  678. )
  679. })
  680. })
  681. describe('help menu', function () {
  682. it('shows hotkeys modal correctly', function () {
  683. const scope = mockScope()
  684. cy.mount(
  685. <EditorProviders
  686. scope={scope}
  687. layoutContext={{ leftMenuShown: true }}
  688. >
  689. <EditorLeftMenu />
  690. </EditorProviders>
  691. )
  692. cy.findByRole('button', { name: 'Show Hotkeys' }).click()
  693. cy.findByText('Hotkeys')
  694. })
  695. it('shows correct url for documentation', function () {
  696. const scope = mockScope()
  697. cy.mount(
  698. <EditorProviders
  699. scope={scope}
  700. layoutContext={{ leftMenuShown: true }}
  701. >
  702. <EditorLeftMenu />
  703. </EditorProviders>
  704. )
  705. cy.findByRole('link', { name: 'Documentation' }).should(
  706. 'have.attr',
  707. 'href',
  708. '/learn'
  709. )
  710. })
  711. it('shows correct contact us modal', function () {
  712. const scope = mockScope()
  713. cy.mount(
  714. <EditorProviders
  715. scope={scope}
  716. layoutContext={{ leftMenuShown: true }}
  717. >
  718. <EditorLeftMenu />
  719. </EditorProviders>
  720. )
  721. cy.findByRole('button', { name: 'Contact us' }).click()
  722. cy.findByText('Affected project URL (Optional)')
  723. })
  724. })
  725. })
  726. describe('for anonymous users', function () {
  727. it('render minimal menu', function () {
  728. const scope = mockScope()
  729. window.metaAttributesCache.set('ol-anonymous', true)
  730. Object.assign(getMeta('ol-ExposedSettings'), { ieeeBrandId: 123 })
  731. cy.mount(
  732. <EditorProviders scope={scope} layoutContext={{ leftMenuShown: true }}>
  733. <EditorLeftMenu />
  734. </EditorProviders>
  735. )
  736. // Download Menu
  737. cy.findByRole('heading', { name: 'Download' })
  738. cy.findByRole('link', { name: 'Source' })
  739. cy.findByRole('link', { name: 'PDF' })
  740. // Actions Menu
  741. cy.findByRole('heading', { name: 'Actions' }).should('not.exist')
  742. cy.findByRole('button', { name: 'Copy project' }).should('not.exist')
  743. cy.findByRole('button', { name: 'Word Count' }).should('not.exist')
  744. // Sync Menu
  745. cy.findByRole('heading', { name: 'Sync' }).should('not.exist')
  746. cy.findByRole('button', { name: 'Dropbox' }).should('not.exist')
  747. cy.findByRole('button', { name: 'Git' }).should('not.exist')
  748. cy.findByRole('button', { name: 'GitHub' }).should('not.exist')
  749. // Settings Menu
  750. cy.findByRole('heading', { name: 'Settings' }).should('not.exist')
  751. cy.findByLabelText('Compiler').should('not.exist')
  752. cy.findByLabelText('TeX Live version').should('not.exist')
  753. cy.findByLabelText('Main document').should('not.exist')
  754. cy.findByLabelText('Spell check').should('not.exist')
  755. cy.findByLabelText('Auto-complete').should('not.exist')
  756. cy.findByLabelText('Auto-close brackets').should('not.exist')
  757. cy.findByLabelText('Code check').should('not.exist')
  758. cy.findByLabelText('Editor theme').should('not.exist')
  759. cy.findByLabelText('Overall theme').should('not.exist')
  760. cy.findByLabelText('Keybindings').should('not.exist')
  761. cy.findByLabelText('Font Size').should('not.exist')
  762. cy.findByLabelText('Font Family').should('not.exist')
  763. cy.findByLabelText('Line Height').should('not.exist')
  764. cy.findByLabelText('PDF Viewer').should('not.exist')
  765. // Help Menu
  766. cy.findByRole('heading', { name: 'Help' })
  767. cy.findByRole('button', { name: 'Show Hotkeys' })
  768. cy.findByRole('button', { name: 'Documentation' }).should('not.exist')
  769. cy.findByRole('link', { name: 'Contact us' }).should('not.exist')
  770. })
  771. })
  772. })