editor-left-menu.spec.tsx 26 KB

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