diff --git a/.gitignore b/.gitignore index a547bf36..b88c8135 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ dist-ssr *.njsproj *.sln *.sw? +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/README.md b/README.md index abaf4c91..d953ffce 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Mongo Modeler is a free, open source online web application that allows you to design and model MongoDB databases. -Now you can take a look at the desktop community preview at [mongomodeler.com](https://mongomodeler.com/). +Give it a try: [mongomodeler.com](https://mongomodeler.com/). Click to watch the video tutorial: @@ -25,8 +25,6 @@ Main features: - It is a 100% online application, no installation required. - It is a free and open source application. -We are working to release a 0.9 version in the coming months. - ## Installation Clone this repository: diff --git a/e2e/launch-mongodb-designer-link.spec.ts b/e2e/launch-mongodb-designer-link.spec.ts new file mode 100644 index 00000000..22501931 --- /dev/null +++ b/e2e/launch-mongodb-designer-link.spec.ts @@ -0,0 +1,8 @@ +import { test, expect } from '@playwright/test'; + +test('navigates to and verifies MongoDB Designer URL', async ({ page }) => { + await page.goto(''); + + await page.getByRole('link', { name: 'Launch MongoDB Designer' }).click(); + await expect(page).toHaveURL('http://localhost:5173/editor.html'); +}); diff --git a/package-lock.json b/package-lock.json index 5a456f90..73c9c072 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "@playwright/test": "^1.48.1", "@testing-library/dom": "^9.3.4", "@testing-library/react": "^14.2.1", "@types/jest": "^29.5.11", @@ -1214,6 +1215,21 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@playwright/test": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.1.tgz", + "integrity": "sha512-s9RtWoxkOLmRJdw3oFvhFbs9OJS0BzrLUc8Hf6l2UdCNd1rqeEyD4BhCJkvzeEoD1FsK4mirsWwGerhVmYKtZg==", + "dev": true, + "dependencies": { + "playwright": "1.48.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.9.6", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", @@ -5336,6 +5352,50 @@ "pathe": "^1.1.0" } }, + "node_modules/playwright": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.1.tgz", + "integrity": "sha512-j8CiHW/V6HxmbntOfyB4+T/uk08tBy6ph0MpBXwuoofkSnLmlfdYNNkFTYD6ofzzlSqLA1fwH4vwvVFvJgLN0w==", + "dev": true, + "dependencies": { + "playwright-core": "1.48.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.1.tgz", + "integrity": "sha512-Yw/t4VAFX/bBr1OzwCuOMZkY1Cnb4z/doAFSwf4huqAGWmf9eMNjmK7NiOljCdLmxeRYcGPPmcDgU0zOlzP0YA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.4.33", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", @@ -7743,6 +7803,15 @@ "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", "dev": true }, + "@playwright/test": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.1.tgz", + "integrity": "sha512-s9RtWoxkOLmRJdw3oFvhFbs9OJS0BzrLUc8Hf6l2UdCNd1rqeEyD4BhCJkvzeEoD1FsK4mirsWwGerhVmYKtZg==", + "dev": true, + "requires": { + "playwright": "1.48.1" + } + }, "@rollup/rollup-android-arm-eabi": { "version": "4.9.6", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", @@ -10683,6 +10752,31 @@ "pathe": "^1.1.0" } }, + "playwright": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.1.tgz", + "integrity": "sha512-j8CiHW/V6HxmbntOfyB4+T/uk08tBy6ph0MpBXwuoofkSnLmlfdYNNkFTYD6ofzzlSqLA1fwH4vwvVFvJgLN0w==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.48.1" + }, + "dependencies": { + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + } + } + }, + "playwright-core": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.1.tgz", + "integrity": "sha512-Yw/t4VAFX/bBr1OzwCuOMZkY1Cnb4z/doAFSwf4huqAGWmf9eMNjmK7NiOljCdLmxeRYcGPPmcDgU0zOlzP0YA==", + "dev": true + }, "postcss": { "version": "8.4.33", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", diff --git a/package.json b/package.json index c69851a4..270626c3 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,17 @@ "version": "0.0.0", "type": "module", "scripts": { + "postinstall": "npm run install:e2e-browsers", + "install:e2e-browsers": "npx playwright install", "dev": "vite", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", "format": "prettier --write .", "test": "vitest", - "prepare": "husky install", - "tsc-check": "tsc --noEmit" + "prepare": "husky || \"No need to install husky\"", + "tsc-check": "tsc --noEmit", + "e2e": "playwright test --ui" }, "dependencies": { "@lemoncode/fonk": "^1.5.4", @@ -26,6 +29,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "@playwright/test": "^1.48.1", "@testing-library/dom": "^9.3.4", "@testing-library/react": "^14.2.1", "@types/jest": "^29.5.11", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..02fd2236 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,59 @@ +import { defineConfig, devices } from '@playwright/test'; + +const BASE_URL = 'http://localhost:5173/'; + +export default defineConfig({ + testDir: './e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + baseURL: BASE_URL, + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run dev', + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/public/assets/fran-lopez.jpg b/public/assets/fran-lopez.jpg new file mode 100644 index 00000000..cd65ba7f Binary files /dev/null and b/public/assets/fran-lopez.jpg differ diff --git a/src/pods/about/members.ts b/src/pods/about/members.ts index 5dc03234..c2e1f531 100644 --- a/src/pods/about/members.ts +++ b/src/pods/about/members.ts @@ -78,4 +78,11 @@ export const memberList: Member[] = [ urlLinkedin: 'https://www.linkedin.com/in/deletidev/', image: './assets/leticia-de-la-osa.jpeg', }, + { + id: '11', + name: 'Fran', + surname: 'López', + urlLinkedin: 'https://linkedin.com/in/francisco-l%C3%B3pez-torres', + image: './assets/fran-lopez.jpg', + }, ]; diff --git a/src/pods/edit-table/components/commands/commands.component.tsx b/src/pods/edit-table/components/commands/commands.component.tsx index 544eadc1..729eef16 100644 --- a/src/pods/edit-table/components/commands/commands.component.tsx +++ b/src/pods/edit-table/components/commands/commands.component.tsx @@ -3,7 +3,7 @@ import { CommandIconButton } from './command-icon-button'; import { FieldVm } from '../../edit-table.vm'; import { GUID, GenerateGUID } from '@/core/model'; import { isFirstItemInArray, isLastItemInArray } from './commands.business'; -import { AddFolder, UpIcon, DownIcon, RemoveIcon } from '@/common/components'; +import { AddFolder, UpIcon, DownIcon, TrashIcon } from '@/common/components'; interface Props { onDeleteField: (fieldId: GUID) => void; @@ -50,7 +50,7 @@ export const Commands: React.FC = (props: Props) => { )} {isDeleteVisible && ( } + icon={} onClick={() => onDeleteField(field.id)} ariaLabel={REMOVE_ICON + field.name} /> diff --git a/vite.config.ts b/vite.config.ts index f87dbe61..be9808d7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,16 +2,7 @@ import { defineConfig } from 'vite'; import { fileURLToPath, URL } from 'node:url'; import react from '@vitejs/plugin-react'; import checker from 'vite-plugin-checker'; -import type { UserConfig as VitestUserConfigInterface } from 'vitest/config'; - -const vitestConfig: VitestUserConfigInterface = { - test: { - setupFiles: ['./vitest.setup.ts'], - globals: true, - restoreMocks: true, - environment: 'jsdom', - }, -}; +import { configDefaults } from 'vitest/config'; // https://vitejs.dev/config/ export default defineConfig({ @@ -24,7 +15,14 @@ export default defineConfig({ }, }, }, - test: vitestConfig.test, + test: { + setupFiles: ['./vitest.setup.ts'], + globals: true, + environment: 'jsdom', + restoreMocks: true, + include: ['./src/**/*.spec.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + exclude: [...configDefaults.exclude, 'e2e/*'], + }, resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)),