diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 0cd82a07..920542b7 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -10,7 +10,6 @@ env: org.opencontainers.image.vendor=${{github.repository_owner}}, org.opencontainers.image.licenses="MIT", org.opencontainers.image.version=${{github.ref_name}}, - org.opencontainers.image.created=${{ env.NOW }}, org.opencontainers.image.source=${{github.server_url}}/${{github.repository}}, org.opencontainers.image.revision=${{github.sha}}, org.opencontainers.image.url="https://umami.is/", diff --git a/.gitignore b/.gitignore index 050397c9..8f39d0f1 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ yarn-error.log* # local env files .env .env.* +*.env.* *.dev.yml diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 00000000..5bed49b8 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'cypress'; + +export default defineConfig({ + e2e: { + baseUrl: 'http://localhost:3000', + }, +}); diff --git a/cypress/e2e/login.cy.ts b/cypress/e2e/login.cy.ts new file mode 100644 index 00000000..f0490560 --- /dev/null +++ b/cypress/e2e/login.cy.ts @@ -0,0 +1,12 @@ +describe('Login test', () => { + it('logs user in with correct credentials and logs user out', () => { + cy.visit('/login'); + cy.dataCy('input-username').type(Cypress.env('umami_user')); + cy.dataCy('input-password').type(Cypress.env('umami_password')); + cy.dataCy('button-submit').click(); + cy.url().should('eq', Cypress.config().baseUrl + '/dashboard'); + cy.dataCy('button-profile').click(); + cy.dataCy('item-logout').click(); + cy.url().should('eq', Cypress.config().baseUrl + '/login'); + }); +}); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts new file mode 100644 index 00000000..2ee2ed8e --- /dev/null +++ b/cypress/support/e2e.ts @@ -0,0 +1,5 @@ +/// + +Cypress.Commands.add('dataCy', value => { + return cy.get(`[data-cy=${value}]`); +}); diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts new file mode 100644 index 00000000..da94c844 --- /dev/null +++ b/cypress/support/index.d.ts @@ -0,0 +1,11 @@ +/// + +declare namespace Cypress { + interface Chainable { + /** + * Custom command to select DOM element by data-cy attribute. + * @example cy.dataCy('greeting') + */ + dataCy(value: string): Chainable>; + } +} diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json new file mode 100644 index 00000000..18edb199 --- /dev/null +++ b/cypress/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["es5", "dom"], + "types": ["cypress", "node"] + }, + "include": ["**/*.ts"] +} diff --git a/package.json b/package.json index 4a38c4e8..24946e3f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "umami", - "version": "2.10.0", + "version": "2.11.0", "description": "A simple, fast, privacy-focused alternative to Google Analytics.", "author": "Mike Cao ", "license": "MIT", @@ -42,7 +42,8 @@ "change-password": "node scripts/change-password.js", "lint": "next lint --quiet", "prepare": "node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky install", - "postbuild": "node scripts/postbuild.js" + "postbuild": "node scripts/postbuild.js", + "cypress-open": "cypress open" }, "lint-staged": { "**/*.{js,jsx,ts,tsx}": [ @@ -133,6 +134,7 @@ "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "cross-env": "^7.0.3", + "cypress": "^13.6.6", "esbuild": "^0.17.17", "eslint": "^8.33.0", "eslint-config-next": "^14.0.4", diff --git a/src/app/login/LoginForm.tsx b/src/app/login/LoginForm.tsx index 28d79458..03192413 100644 --- a/src/app/login/LoginForm.tsx +++ b/src/app/login/LoginForm.tsx @@ -42,17 +42,30 @@ export function LoginForm() {
umami
- + - + - + {formatMessage(labels.login)} diff --git a/src/components/input/ProfileButton.tsx b/src/components/input/ProfileButton.tsx index 11cf1613..4b3c3f7b 100644 --- a/src/components/input/ProfileButton.tsx +++ b/src/components/input/ProfileButton.tsx @@ -25,7 +25,7 @@ export function ProfileButton() { return ( -