diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a69868c0..5498b747 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,6 +9,11 @@ on: jobs: cypress-run: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # run 3 copies of the current job in parallel + containers: [1, 2, 3] steps: - name: Checkout @@ -30,12 +35,6 @@ jobs: node-version: 16 cache: 'yarn' - - name: Lint & Test - run: | - yarn install - yarn lint - yarn test:unit - # Install NPM dependencies, cache them correctly # and run all Cypress tests - name: Cypress run @@ -46,5 +45,6 @@ jobs: wait-on: 'http://localhost:3000' record: true headless: true + parallel: true env: CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 00000000..ee582139 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,36 @@ +name: Unit Tests + +on: + pull_request: + branches: + - master + - develop + +jobs: + unit-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - uses: actions/cache@v3 + id: yarn-and-build-cache + with: + path: | + build + node_modules + key: ${{ runner.os }}-node_modules-build-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-node_modules-build- + + - uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'yarn' + + - name: Lint & Test + run: | + yarn install + yarn lint + yarn test:unit diff --git a/cypress.config.js b/cypress.config.js index c394294b..40248bb5 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -8,6 +8,10 @@ export default defineConfig({ snapshotFileName: './cypress/snapshots.js', defaultCommandTimeout: 16000, requestTimeout: 16000, + retries: { + runMode: 2, + openMode: 0 + }, e2e: { setupNodeEvents(on, config) { on('task', { @@ -26,6 +30,6 @@ export default defineConfig({ }); }, baseUrl: 'http://localhost:3000', - specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}' + specPattern: 'cypress/e2e/**/*.spec.ts' } }); diff --git a/cypress/e2e/actions.spec.ts b/cypress/e2e/actions.spec.ts index 5b46a37a..78e818e4 100644 --- a/cypress/e2e/actions.spec.ts +++ b/cypress/e2e/actions.spec.ts @@ -1,7 +1,9 @@ +import { disableDebounce } from './util'; describe('Check actions', () => { beforeEach(() => { cy.clearLocalStorage(); cy.visit('/edit'); + disableDebounce(); }); it('should update markdown code', () => { @@ -24,8 +26,7 @@ describe('Check actions', () => { }); it('should download png and svg', () => { - const now = new Date(2022, 0, 1).getTime(); - cy.clock(now); + cy.clock(new Date(2022, 0, 1).getTime()); const downloadsFolder = Cypress.config('downloadsFolder'); const verifyFileSize = (fileType: string, size: number) => { diff --git a/cypress/e2e/diagramUpdate.spec.ts b/cypress/e2e/diagramUpdate.spec.ts index 63726d1b..3f7b495c 100644 --- a/cypress/e2e/diagramUpdate.spec.ts +++ b/cypress/e2e/diagramUpdate.spec.ts @@ -1,16 +1,10 @@ -describe('Auto sync tests', () => { - const cmd = Cypress.platform === 'darwin' ? 'meta' : 'ctrl'; - const getEditor = ({ bottom = true, newline = false } = {}) => - cy - .get('#editor textarea:first') - .click() - .focused() - .type(`${bottom ? '{pageDown}' : `{${cmd}}`}`) - .type(`${newline ? '{enter}' : `{${cmd}}`}`); +import { getEditor, cmd, disableDebounce } from './util'; +describe('Auto sync tests', () => { beforeEach(() => { cy.clearLocalStorage(); cy.visit('/'); + disableDebounce(); }); it('should dim diagram when code is edited', () => { @@ -26,7 +20,7 @@ describe('Auto sync tests', () => { cy.get('#view').should('not.have.class', 'outOfSync'); getEditor().type(' C --> Test'); cy.get('#view').should('have.class', 'outOfSync'); - getEditor().type(`{${cmd}}{enter}`); + getEditor().type(`${cmd}{enter}`); cy.get('#view').should('not.have.class', 'outOfSync'); }); @@ -52,10 +46,10 @@ describe('Auto sync tests', () => { }); it('supports commenting code out/in', () => { - getEditor().type(`{uparrow}{${cmd}}/`); + getEditor().type(`{uparrow}${cmd}/`); cy.get('#view').contains('Car').should('not.exist'); - getEditor().type(`{uparrow}{${cmd}}/`); + getEditor().type(`{uparrow}${cmd}/`); cy.get('#view').contains('Car').should('exist'); }); @@ -73,3 +67,18 @@ describe('Auto sync tests', () => { .should('exist'); }); }); + +describe.only('Pan and Zoom', () => { + beforeEach(() => { + cy.clearLocalStorage(); + cy.visit('/'); + disableDebounce(); + }); + it('should toggle pan and zoom', () => { + cy.get('#svg-pan-zoom-reset-pan-zoom').should('not.exist'); + cy.contains('Pan & Zoom').click(); + cy.get('#svg-pan-zoom-reset-pan-zoom').should('exist'); + cy.contains('Pan & Zoom').click(); + cy.get('#svg-pan-zoom-reset-pan-zoom').should('not.exist'); + }); +}); diff --git a/cypress/e2e/history.spec.ts b/cypress/e2e/history.spec.ts index a769a3c9..92b558d8 100644 --- a/cypress/e2e/history.spec.ts +++ b/cypress/e2e/history.spec.ts @@ -1,8 +1,11 @@ +import { getEditor, disableDebounce } from './util'; + describe('Save History', () => { beforeEach(() => { cy.clock(); cy.clearLocalStorage(); cy.visit('/edit'); + disableDebounce(); cy.contains('Actions').click(); cy.contains('History').click(); }); @@ -18,14 +21,14 @@ describe('Save History', () => { expect(str).to.equal('State already saved.'); }); cy.on('window:confirm', () => true); - cy.get('#editor').type(' C --> HistoryTest'); + getEditor().type(' C --> HistoryTest'); cy.get('#saveHistory').click(); cy.get('#historyList').find('li').should('have.length', 2); }); it('should be able to restore and delete', () => { cy.get('#saveHistory').click(); - cy.get('#editor').type(' C --> HistoryTest'); + getEditor().type(' C --> HistoryTest'); cy.get('#historyList').find('No items in History').should('not.exist'); cy.get('#historyList').find('li').should('have.length', 1); cy.contains('HistoryTest'); @@ -35,7 +38,7 @@ describe('Save History', () => { cy.get('#historyList').find('li').should('have.length', 0); cy.get('#historyList').contains('No items in History'); cy.get('#saveHistory').click(); - cy.get('#editor').type(' C --> HistoryTest'); + getEditor().type(' C --> HistoryTest'); cy.get('#saveHistory').click(); cy.get('#editor').type('ing'); cy.get('#clearHistory').click(); @@ -48,7 +51,7 @@ describe('Save History', () => { // TODO: Fix #639 xit('should auto save history', () => { - cy.get('#editor').type(' C --> HistoryTest'); + getEditor().type(' C --> HistoryTest'); cy.tick(70000); cy.contains('Timeline').click(); cy.get('#historyList').find('li').should('have.length', 1); diff --git a/cypress/e2e/loadSite.spec.ts b/cypress/e2e/loadSite.spec.ts index 83cab087..50497339 100644 --- a/cypress/e2e/loadSite.spec.ts +++ b/cypress/e2e/loadSite.spec.ts @@ -1,11 +1,13 @@ import { toBase64 } from 'js-base64'; +import { disableDebounce } from './util'; describe('Site Loads', () => { beforeEach(() => { cy.clearLocalStorage(); + cy.visit('/'); + disableDebounce(); }); it('Check Home page load', () => { - cy.visit('/'); cy.url().should('include', '/edit'); cy.contains('History').click(); cy.getLocalStorage('codeStore').snapshot(); @@ -15,12 +17,7 @@ describe('Site Loads', () => { cy.visit( '/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAgICBBW0NocmlzdG1hc10gLS0-fEdldCBtb25leXwgQihHbyBzaG9wcGluZylcbiAgICBCIC0tPiBDe0xldCBtZSB0aGlua31cbiAgICBDIC0tPnxPbmV8IERbTGFwdG9wXVxuICAgIEMgLS0-fFR3b3wgRVtpUGhvbmVdXG4gICAgQyAtLT58VGhyZWV8IEZbZmE6ZmEtY2FyIENhcl0iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ' ); - cy.url().should( - 'include', - '/edit#pako:eNpVkM1qw0AMhF9F6NRC_AI-FBo7zSXQQHLz-iC8cnZJ9oe1TAm2373rmkKrk9B8MwyasAuascRbomjgWisPed6byiQ7iKOhhaJ4m48s4ILn5wz7l2OAwYQYrb-9bvx-haCaTivGIMb6-7JJ1Y__0_MMdXOiKCG2f5XrV5jh0NizyfH_FZM4uz6ansqeio4SVJRa3KHj5MjqXHtaDQrFsGOFZV419zQ-RKHyS0bHqEn4oK2EhDnmMfAOaZRwefoOS0kj_0K1pfwFtx2XbzAdW4g' - ); - cy.contains('History').click(); - cy.getLocalStorage('codeStore').snapshot(); + cy.url().should('include', '/edit#pako:eNp'); }); it('should load sample diagrams when clicked', () => { @@ -91,6 +88,7 @@ describe('Site Loads', () => { it('should show troubleshooting steps if loading fails', () => { cy.visit('/#/edit/eyJjb2RlIjoiZ3JhcGggVERcbiAg'); + cy.reload(true); cy.contains('Please Click here to Raise an issue in github.'); }); @@ -117,7 +115,6 @@ describe('Site Loads', () => { cy.visit( '/edit#pako:eNptkU1PwzAMhv9K5BOI9Q9EXBDbJA477YYqITcxndV8QD40weh_Jy1rGR0-OY_tV2_sEyivCSQogzGuGduAtnaixINji0bcf1WVWGfVXdMtx8M1faYm4B8sxR27JLClJd6nwK4VLTlN4bI4jMQd2pLe3C4KFhNNcLQ92jv9ADGLNoTdozc-zIV4ZDsNlud7RtVN7_5Sb_jYrFcN3iN_0pPbEqUZK3QbTP_Ojyv4NdR4bwTHlyMbPcOQ3WJ2CliBpWCRdbnLqFJDOpClGmRJNYauhtr1pS-_6bKMjebkA8hXNJFWgDn5_YdTIFPINDWdb3vu6r8BaWOZRQ' ); - cy.reload(); cy.contains('Animal'); }); }); diff --git a/cypress/e2e/util.ts b/cypress/e2e/util.ts new file mode 100644 index 00000000..e715fd36 --- /dev/null +++ b/cypress/e2e/util.ts @@ -0,0 +1,11 @@ +export const cmd = `{${Cypress.platform === 'darwin' ? 'meta' : 'ctrl'}}`; + +export const getEditor = ({ bottom = true, newline = false } = {}) => + cy + .get('#editor textarea:first') + .click() + .focused() + .type(`${bottom ? '{pageDown}' : cmd}`) + .type(`${newline ? '{enter}' : cmd}`); + +export const disableDebounce = () => cy.setLocalStorage('noDebounce', 'true'); diff --git a/package.json b/package.json index 9a44508d..837d04af 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,8 @@ "monaco-editor": "0.34.0", "monaco-mermaid": "1.0.6", "pako": "2.0.4", - "random-word-slugs": "0.1.6" + "random-word-slugs": "0.1.6", + "svg-pan-zoom": "^3.6.1" }, "lint-staged": { "*.{ts,svelte,js,css,md,json}": [ diff --git a/src/lib/components/editor.svelte b/src/lib/components/editor.svelte index 999f7ded..e9299a5e 100644 --- a/src/lib/components/editor.svelte +++ b/src/lib/components/editor.svelte @@ -24,15 +24,18 @@ }; let oldText = text; $: editor && Monaco?.editor.setModelLanguage(editor.getModel(), language); - $: { - if (text !== oldText) { + + const handleTextUpdate = (newText: string) => { + if (newText !== oldText) { if ($stateStore.updateEditor) { - editor?.setValue(text); + editor?.setValue(newText); } - oldText = text; + oldText = newText; } editor && Monaco?.editor.setModelMarkers(editor.getModel(), 'test', $stateStore.errorMarkers); - } + }; + + $: handleTextUpdate(text); themeStore.subscribe(({ isDark }) => { editor && Monaco?.editor.setTheme(isDark ? 'mermaid-dark' : 'mermaid'); @@ -62,9 +65,9 @@ initEditor(Monaco); editor = Monaco.editor.create(divEl, editorOptions); editor.onDidChangeModelContent(() => { - text = editor.getValue(); + oldText = editor.getValue(); dispatch('update', { - text + text: oldText }); }); editor.addAction({ diff --git a/src/lib/components/preset.svelte b/src/lib/components/preset.svelte index 2c31cbaf..310b73b4 100644 --- a/src/lib/components/preset.svelte +++ b/src/lib/components/preset.svelte @@ -97,7 +97,8 @@ const loadSampleDiagram = (diagramType: string): void => { updateCode(samples[diagramType], { updateDiagram: true, - updateEditor: true + updateEditor: true, + resetPanZoom: true }); }; diff --git a/src/lib/components/view.svelte b/src/lib/components/view.svelte index a4036093..f643d3e1 100644 --- a/src/lib/components/view.svelte +++ b/src/lib/components/view.svelte @@ -1,7 +1,9 @@ @@ -58,16 +108,25 @@