editor-left-menu.spec.tsx 25 KB

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