From a8255b48d238a33a9f96dd9a5a4028fdb0b9b5dd Mon Sep 17 00:00:00 2001 From: M Fahru Date: Tue, 6 Jun 2023 09:42:49 -0700 Subject: [PATCH] Add cypress tests for editor left menu react (#10902) GitOrigin-RevId: b24021911c85eb689519ff7a6f2b0ea7cbc4a574 --- .../editor-left-menu.spec.tsx | 870 ++++++++++++++++++ .../components/editor-left-menu/scope.tsx | 54 ++ 2 files changed, 924 insertions(+) create mode 100644 services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx create mode 100644 services/web/test/frontend/components/editor-left-menu/scope.tsx diff --git a/services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx b/services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx new file mode 100644 index 0000000000..13ed945892 --- /dev/null +++ b/services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx @@ -0,0 +1,870 @@ +import EditorLeftMenu from '../../../../frontend/js/features/editor-left-menu/components/editor-left-menu' +import { + AllowedImageName, + MainDocument, + OverallThemeMeta, + SpellCheckLanguage, +} from '../../../../types/project-settings' +import { EditorProviders } from '../../helpers/editor-providers' +import { mockScope } from './scope' + +describe('', function () { + before(function () { + window.metaAttributesCache = new Map() + }) + + beforeEach(function () { + cy.viewport(800, 800) + cy.interceptCompile() + }) + + afterEach(function () { + window.metaAttributesCache = new Map() + }) + + describe('for non-anonymous users', function () { + const overallThemes: OverallThemeMeta[] = [ + { + name: 'Overall Theme 1', + val: '', + path: 'https://overleaf.com/overalltheme-1.css', + }, + { + name: 'Overall Theme 2', + val: 'light-', + path: 'https://overleaf.com/overalltheme-2.css', + }, + ] + + const allowedImageNames: AllowedImageName[] = [ + { + imageDesc: 'Image 1', + imageName: 'img-1', + }, + { + imageDesc: 'Image 2', + imageName: 'img-2', + }, + ] + + beforeEach(function () { + window.metaAttributesCache.set('ol-overallThemes', overallThemes) + window.metaAttributesCache.set('ol-allowedImageNames', allowedImageNames) + window.metaAttributesCache.set('ol-anonymous', false) + window.metaAttributesCache.set('ol-showSupport', true) + window.metaAttributesCache.set('ol-user', { + email: 'sherlock@holmes.co.uk', + first_name: 'Sherlock', + last_name: 'Holmes', + }) + }) + + afterEach(function () { + window.metaAttributesCache = new Map() + }) + + it('render full menu', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + // Download Menu + cy.findByRole('heading', { name: 'Download' }) + cy.findByRole('link', { name: 'Source' }) + cy.findByRole('link', { name: 'PDF' }) + + // Actions Menu + cy.findByRole('heading', { name: 'Actions' }) + cy.findByRole('button', { name: 'Copy Project' }) + cy.findByRole('button', { name: 'Word Count' }) + + // Sync Menu + cy.findByRole('heading', { name: 'Sync' }) + cy.findByRole('button', { name: 'Dropbox' }) + cy.findByRole('button', { name: 'Git' }) + cy.findByRole('button', { name: 'GitHub' }) + + // Settings Menu + cy.findByRole('heading', { name: 'Settings' }) + cy.findByLabelText('Compiler') + cy.findByLabelText('TeX Live version') + cy.findByLabelText('Main document') + cy.findByLabelText('Spell check') + cy.findByLabelText('Auto-complete') + cy.findByLabelText('Auto-close Brackets') + cy.findByLabelText('Code check') + cy.findByLabelText('Editor theme') + cy.findByLabelText('Overall theme') + cy.findByLabelText('Keybindings') + cy.findByLabelText('Font Size') + cy.findByLabelText('Font Family') + cy.findByLabelText('Line Height') + cy.findByLabelText('PDF Viewer') + + // Help Menu + cy.findByRole('heading', { name: 'Help' }) + cy.findByRole('button', { name: 'Show Hotkeys' }) + cy.findByRole('link', { name: 'Documentation' }) + cy.findByRole('button', { name: 'Contact Us' }) + }) + + describe('download menu', function () { + it('have a correct source & pdf download url', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.findByRole('link', { name: 'Source' }).should( + 'have.attr', + 'href', + '/project/project123/download/zip' + ) + + cy.findByRole('link', { name: 'PDF' }) + .should('have.attr', 'href') + .and('match', /\/download\/project\/project123\/build/) + }) + }) + + describe('actions menu', function () { + it('shows copy project modal correctly', function () { + cy.intercept('POST', '/project/*/clone', { + body: { + project_id: 'new_project_id', + }, + }) + + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.findByRole('button', { name: 'Copy Project' }).click() + cy.findByRole('heading', { name: 'Copy Project' }) + + // try closing & re-opening the modal with different methods + cy.findByRole('button', { name: 'Close' }).click() + cy.findByRole('button', { name: 'Copy Project' }).click() + cy.findByRole('button', { name: 'Cancel' }).click() + cy.findByRole('button', { name: 'Copy Project' }).click() + + cy.findByLabelText('New Name').focus() + cy.findByLabelText('New Name').clear() + cy.findByLabelText('New Name').type('Project Renamed') + cy.get('#clone-project-form-name[value="Project Renamed"') + }) + + it('shows word count modal correctly', function () { + cy.intercept('GET', '/project/*/wordcount*', { + texcount: { + encode: 'ascii', + textWords: 781, + headWords: 66, + outside: 11, + headers: 41, + elements: 2, + mathInline: 6, + mathDisplay: 1, + errors: 0, + }, + }) + + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.findByRole('button', { name: 'Word Count' }).click() + cy.findByText('Total Words:') + cy.findByText('781') + cy.findByText('Headers:') + cy.findByText('41') + cy.findByText('Math Inline:') + cy.findByText('6') + cy.findByText('Math Display:') + cy.findByText('1') + }) + }) + + describe('sync menu', function () { + it('shows dropbox modal correctly', function () { + cy.intercept('GET', '/dropbox/status', { + registered: true, + }) + + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + project: { + members: [], + owner: { + _id: '123', + }, + }, + user: { + features: { + dropbox: false, + }, + }, + }) + + cy.mount( + + + + ) + + cy.findByRole('button', { name: 'Dropbox' }).click() + cy.findByText('Dropbox Sync') + }) + + it('shows git modal correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.findByRole('button', { name: 'Git' }).click() + cy.findByText('Clone with Git') + cy.findByText(/your project using the link displayed below/) + }) + + it('shows github modal correctly', function () { + cy.intercept('GET', '/user/github-sync/status', { + available: false, + enabled: false, + }) + + cy.intercept('GET', '/project/*/github-sync/status', { + enabled: false, + }) + + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.findByRole('button', { name: 'GitHub' }).click() + cy.findByText('GitHub Sync') + cy.findByText('Push to GitHub, pull to Overleaf') + }) + }) + + describe('settings menu', function () { + afterEach(function () { + window.metaAttributesCache = new Map() + }) + + it('shows compiler menu correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get('#settings-menu-compiler option').then( + options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq([ + 'pdflatex', + 'latex', + 'xelatex', + 'lualatex', + ]) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq([ + 'pdfLaTeX', + 'LaTeX', + 'XeLaTeX', + 'LuaLaTeX', + ]) + } + ) + }) + + it('shows texlive version menu correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get('#settings-menu-imageName option').then( + options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq(['img-1', 'img-2']) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq(['Image 1', 'Image 2']) + } + ) + }) + + it('shows document menu correctly', function () { + const docs: MainDocument[] = [ + { + path: 'main.tex', + doc: { + name: 'main.tex', + id: 'id1', + type: 'doc', + selected: false, + }, + }, + { + path: 'main2.tex', + doc: { + name: 'main2.tex', + id: 'id2', + type: 'doc', + selected: false, + }, + }, + ] + + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + docs, + }) + + cy.mount( + + + + ) + + cy.get('#settings-menu-rootDocId option').then( + options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq(['id1', 'id2']) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq(['main.tex', 'main2.tex']) + } + ) + }) + + it('shows spellcheck menu correctly', function () { + const languages: SpellCheckLanguage[] = [ + { + name: 'Lang 1', + code: 'lang-1', + }, + { + name: 'Lang 2', + code: 'lang-2', + }, + ] + + window.metaAttributesCache.set('ol-languages', languages) + + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get( + '#settings-menu-spellCheckLanguage option' + ).then(options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq(['', 'lang-1', 'lang-2']) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq(['Off', 'Lang 1', 'Lang 2']) + }) + }) + + it('shows dictionary modal correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get('label[for="dictionary"] ~ button').click() + cy.findByText('Edit Dictionary') + cy.findByText('Your custom dictionary is empty.') + }) + + it('shows auto-complete menu correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get('#settings-menu-autoComplete option').then( + options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq(['true', 'false']) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq(['On', 'Off']) + } + ) + }) + + it('shows auto-close brackets menu correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get( + '#settings-menu-autoPairDelimiters option' + ).then(options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq(['true', 'false']) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq(['On', 'Off']) + }) + }) + + it('shows code check menu correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get( + '#settings-menu-syntaxValidation option' + ).then(options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq(['true', 'false']) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq(['On', 'Off']) + }) + }) + + it('shows editor theme menu correctly', function () { + const editorThemes = ['editortheme-1', 'editortheme-2', 'editortheme-3'] + + const legacyEditorThemes = [ + 'legacytheme-1', + 'legacytheme-2', + 'legacytheme-3', + ] + + window.metaAttributesCache.set('ol-editorThemes', editorThemes) + window.metaAttributesCache.set( + 'ol-legacyEditorThemes', + legacyEditorThemes + ) + + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get('#settings-menu-editorTheme option').then( + options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq([ + 'editortheme-1', + 'editortheme-2', + 'editortheme-3', + '-', + 'legacytheme-1', + 'legacytheme-2', + 'legacytheme-3', + ]) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq([ + 'editortheme-1', + 'editortheme-2', + 'editortheme-3', + '—————————————————', + 'legacytheme-1', + 'legacytheme-2', + 'legacytheme-3', + ]) + } + ) + }) + + it('shows overall theme menu correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get('#settings-menu-overallTheme option').then( + options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq(['', 'light-']) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq(['Overall Theme 1', 'Overall Theme 2']) + } + ) + }) + + it('shows keybindings menu correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get('#settings-menu-mode option').then( + options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq(['default', 'vim', 'emacs']) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq(['None', 'Vim', 'Emacs']) + } + ) + }) + + it('shows font size menu correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get('#settings-menu-fontSize option').then( + options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq([ + '10', + '11', + '12', + '13', + '14', + '16', + '18', + '20', + '22', + '24', + ]) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq([ + '10px', + '11px', + '12px', + '13px', + '14px', + '16px', + '18px', + '20px', + '22px', + '24px', + ]) + } + ) + }) + + it('shows font family menu correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get('#settings-menu-fontFamily option').then( + options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq(['monaco', 'lucida']) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq([ + 'Monaco / Menlo / Consolas', + 'Lucida / Source Code Pro', + ]) + } + ) + }) + + it('shows line height menu correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get('#settings-menu-lineHeight option').then( + options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq(['compact', 'normal', 'wide']) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq(['Compact', 'Normal', 'Wide']) + } + ) + }) + + it('shows pdf viewer menu correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.get('#settings-menu-pdfViewer option').then( + options => { + const values = [...options].map(o => o.value) + expect(values).to.deep.eq(['pdfjs', 'native']) + + const texts = [...options].map(o => o.text) + expect(texts).to.deep.eq(['Overleaf', 'Browser']) + } + ) + }) + }) + + describe('help menu', function () { + it('shows hotkeys modal correctly', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.findByRole('button', { name: 'Show Hotkeys' }).click() + cy.findByText('Hotkeys (Source editor)') + }) + + it('shows correct url for documentation', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.findByRole('link', { name: 'Documentation' }).should( + 'have.attr', + 'href', + '/learn' + ) + }) + + it('shows correct contact us modal', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + + cy.mount( + + + + ) + + cy.findByRole('button', { name: 'Contact Us' }).click() + cy.findByText('Affected project URL (Optional)') + }) + }) + }) + + describe('for anonymous users', function () { + it('render minimal menu', function () { + const scope = mockScope({ + ui: { + leftMenuShown: true, + }, + }) + window.metaAttributesCache.set('ol-anonymous', true) + + cy.mount( + + + + ) + + // Download Menu + cy.findByRole('heading', { name: 'Download' }) + cy.findByRole('link', { name: 'Source' }) + cy.findByRole('link', { name: 'PDF' }) + + // Actions Menu + cy.findByRole('heading', { name: 'Actions' }).should('not.exist') + cy.findByRole('button', { name: 'Copy Project' }).should('not.exist') + cy.findByRole('button', { name: 'Word Count' }).should('not.exist') + + // Sync Menu + cy.findByRole('heading', { name: 'Sync' }).should('not.exist') + cy.findByRole('button', { name: 'Dropbox' }).should('not.exist') + cy.findByRole('button', { name: 'Git' }).should('not.exist') + cy.findByRole('button', { name: 'GitHub' }).should('not.exist') + + // Settings Menu + cy.findByRole('heading', { name: 'Settings' }).should('not.exist') + cy.findByLabelText('Compiler').should('not.exist') + cy.findByLabelText('TeX Live version').should('not.exist') + cy.findByLabelText('Main document').should('not.exist') + cy.findByLabelText('Spell check').should('not.exist') + cy.findByLabelText('Auto-complete').should('not.exist') + cy.findByLabelText('Auto-close Brackets').should('not.exist') + cy.findByLabelText('Code check').should('not.exist') + cy.findByLabelText('Editor theme').should('not.exist') + cy.findByLabelText('Overall theme').should('not.exist') + cy.findByLabelText('Keybindings').should('not.exist') + cy.findByLabelText('Font Size').should('not.exist') + cy.findByLabelText('Font Family').should('not.exist') + cy.findByLabelText('Line Height').should('not.exist') + cy.findByLabelText('PDF Viewer').should('not.exist') + + // Help Menu + cy.findByRole('heading', { name: 'Help' }) + cy.findByRole('button', { name: 'Show Hotkeys' }) + cy.findByRole('button', { name: 'Documentation' }).should('not.exist') + cy.findByRole('link', { name: 'Contact Us' }).should('not.exist') + }) + }) +}) diff --git a/services/web/test/frontend/components/editor-left-menu/scope.tsx b/services/web/test/frontend/components/editor-left-menu/scope.tsx new file mode 100644 index 0000000000..f36ff5ef80 --- /dev/null +++ b/services/web/test/frontend/components/editor-left-menu/scope.tsx @@ -0,0 +1,54 @@ +import { MainDocument, PdfViewer } from '../../../../types/project-settings' + +type Scope = { + settings?: { + syntaxValidation?: boolean + pdfViewer?: PdfViewer + } + editor?: { + sharejs_doc?: { + doc_id?: string + getSnapshot?: () => string + } + newSourceEditor?: boolean + } + hasLintingError?: boolean + ui?: { + view?: 'editor' | 'history' | 'file' | 'pdf' + pdfLayout?: 'flat' | 'sideBySide' | 'split' + leftMenuShown?: boolean + } + project?: { + members?: any[] + owner: { + _id: string + } + } + user?: { + features?: { + dropbox: boolean + } + } + docs?: MainDocument[] +} + +export const mockScope = (scope?: Scope) => ({ + settings: { + syntaxValidation: false, + pdfViewer: 'pdfjs', + }, + editor: { + sharejs_doc: { + doc_id: 'test-doc', + getSnapshot: () => 'some doc content', + }, + newSourceEditor: true, + }, + hasLintingError: false, + ui: { + view: 'editor', + pdfLayout: 'sideBySide', + leftMenuShown: false, + }, + ...scope, +})