editor-left-menu.spec.tsx 25 KB

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