mirror of
https://github.com/bretzel-app/crumbs.git
synced 2026-10-13 04:06:57 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd9762600e | ||
|
|
f1ee1744e7 | ||
|
|
40e32ae8fc | ||
|
|
c1c4fe5056 | ||
|
|
e54e706b65 |
@@ -54,11 +54,3 @@ jobs:
|
|||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
with:
|
with:
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
|
||||||
deploy:
|
|
||||||
name: Deploy
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: release
|
|
||||||
steps:
|
|
||||||
- name: Trigger Dokploy redeploy
|
|
||||||
run: curl -X POST "${{ secrets.DOKPLOY_WEBHOOK_URL }}"
|
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
crumbs:
|
crumbs:
|
||||||
build: .
|
image: ghcr.io/bretzel-app/crumbs:0.2.2
|
||||||
container_name: crumbs
|
container_name: crumbs
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
@@ -10,7 +10,7 @@ services:
|
|||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- DATA_DIR=/data
|
- DATA_DIR=/data
|
||||||
- DATABASE_URL=/data/crumbs.db
|
- DATABASE_URL=/data/crumbs.db
|
||||||
- ORIGIN=http://localhost:3000
|
- ORIGIN=${ORIGIN:-http://localhost:3000}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "crumbs",
|
"name": "crumbs",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.2.0",
|
"version": "0.2.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
|||||||
+5
-1
@@ -60,7 +60,11 @@ echo "Bumping $BUMP version..."
|
|||||||
npm version "$BUMP" -m "chore: bump version to %s" --no-git-tag-version
|
npm version "$BUMP" -m "chore: bump version to %s" --no-git-tag-version
|
||||||
|
|
||||||
NEW_VERSION=$(node -p "require('./package.json').version")
|
NEW_VERSION=$(node -p "require('./package.json').version")
|
||||||
git add package.json
|
|
||||||
|
# Pin docker-compose image to the new version tag
|
||||||
|
sed -i '' "s|image: ghcr.io/bretzel-app/crumbs:.*|image: ghcr.io/bretzel-app/crumbs:${NEW_VERSION}|" docker-compose.yml
|
||||||
|
|
||||||
|
git add package.json docker-compose.yml
|
||||||
git commit -m "chore: bump version to $NEW_VERSION"
|
git commit -m "chore: bump version to $NEW_VERSION"
|
||||||
git tag "v$NEW_VERSION"
|
git tag "v$NEW_VERSION"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { test, expect } from './helpers/fixtures.js';
|
import { test, expect, noteCard } from './helpers/fixtures.js';
|
||||||
|
|
||||||
test.describe('Checklist', () => {
|
test.describe('Checklist', () => {
|
||||||
test('Scenario: Checklist UI replaces editor when checklist mode is enabled', async ({ authenticatedPage: page }) => {
|
test('Scenario: Checklist UI replaces editor when checklist mode is enabled', async ({ authenticatedPage: page }) => {
|
||||||
@@ -27,7 +27,7 @@ test.describe('Checklist', () => {
|
|||||||
await expect(page.getByText('Shopping List')).toBeVisible();
|
await expect(page.getByText('Shopping List')).toBeVisible();
|
||||||
|
|
||||||
// When the user reopens the note
|
// When the user reopens the note
|
||||||
await page.getByTestId('note-card').click();
|
await noteCard(page, 'Shopping List').click();
|
||||||
|
|
||||||
// Then the checklist is displayed with the saved item
|
// Then the checklist is displayed with the saved item
|
||||||
await expect(page.getByTestId('checklist')).toBeVisible();
|
await expect(page.getByTestId('checklist')).toBeVisible();
|
||||||
@@ -43,12 +43,12 @@ test.describe('Checklist', () => {
|
|||||||
await page.getByTestId('close-editor-btn').click();
|
await page.getByTestId('close-editor-btn').click();
|
||||||
|
|
||||||
// When the user reopens the note and checks the item
|
// When the user reopens the note and checks the item
|
||||||
await page.getByTestId('note-card').click();
|
await noteCard(page, 'Tasks').click();
|
||||||
await page.getByTestId('checklist-checkbox').first().check();
|
await page.getByTestId('checklist-checkbox').first().check();
|
||||||
await page.getByTestId('close-editor-btn').click();
|
await page.getByTestId('close-editor-btn').click();
|
||||||
|
|
||||||
// Then reopening the note shows the item is still checked
|
// Then reopening the note shows the item is still checked
|
||||||
await page.getByTestId('note-card').click();
|
await noteCard(page, 'Tasks').click();
|
||||||
await expect(page.getByTestId('checklist-checkbox').first()).toBeChecked();
|
await expect(page.getByTestId('checklist-checkbox').first()).toBeChecked();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { test, expect } from './helpers/fixtures.js';
|
import { test, expect, noteCard } from './helpers/fixtures.js';
|
||||||
import type { Page, Locator } from '@playwright/test';
|
import type { Page, Locator } from '@playwright/test';
|
||||||
|
|
||||||
/** Run a TipTap command chain via the exposed editor instance on the DOM element */
|
/** Run a TipTap command chain via the exposed editor instance on the DOM element */
|
||||||
@@ -94,7 +94,7 @@ test.describe('Rich text formatting', () => {
|
|||||||
await page.getByTestId('close-editor-btn').click();
|
await page.getByTestId('close-editor-btn').click();
|
||||||
|
|
||||||
// When the user reopens the note
|
// When the user reopens the note
|
||||||
await page.getByTestId('note-card').click();
|
await noteCard(page, 'Rich Text Note').click();
|
||||||
|
|
||||||
// Then the content is rendered as rich text in the TipTap editor
|
// Then the content is rendered as rich text in the TipTap editor
|
||||||
await expect(page.getByTestId('tiptap-editor')).toBeVisible();
|
await expect(page.getByTestId('tiptap-editor')).toBeVisible();
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { test as base, expect, type Page } from '@playwright/test';
|
import { test as base, expect, type Page, type Locator } from '@playwright/test';
|
||||||
|
|
||||||
const TEST_PASSWORD = 'testpassword123';
|
const TEST_PASSWORD = 'testpassword123';
|
||||||
|
|
||||||
|
/** Find a specific note card by its title text */
|
||||||
|
export function noteCard(page: Page, title: string): Locator {
|
||||||
|
return page.locator('[data-testid="note-card"]', { hasText: title });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extended test fixture that handles setup/auth.
|
* Extended test fixture that handles setup/auth.
|
||||||
*/
|
*/
|
||||||
@@ -40,6 +45,10 @@ export async function setupAndLogin(page: Page) {
|
|||||||
await page.waitForURL('/');
|
await page.waitForURL('/');
|
||||||
}
|
}
|
||||||
// else: already on main page (session still valid)
|
// else: already on main page (session still valid)
|
||||||
|
|
||||||
|
// Wait for full hydration after auth redirect (window.location.href causes full reload).
|
||||||
|
// networkidle ensures JS bundles have loaded and executed.
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
}
|
}
|
||||||
|
|
||||||
export { expect };
|
export { expect };
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import { test, expect } from './helpers/fixtures.js';
|
import { test, expect, noteCard } from './helpers/fixtures.js';
|
||||||
|
|
||||||
test.describe('Notes CRUD', () => {
|
test.describe('Notes CRUD', () => {
|
||||||
test('Scenario: Fresh account shows an empty notes list', async ({ authenticatedPage: page }) => {
|
|
||||||
// Given the user has no notes
|
|
||||||
// Then the empty state message is displayed
|
|
||||||
await expect(page.getByText('No crumbs yet')).toBeVisible();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Scenario: New note appears in the notes list after creation', async ({ authenticatedPage: page }) => {
|
test('Scenario: New note appears in the notes list after creation', async ({ authenticatedPage: page }) => {
|
||||||
// When the user creates a note titled "My First Note" with content "Hello world!"
|
// When the user creates a note titled "My First Note" with content "Hello world!"
|
||||||
await page.getByTestId('new-note-btn').click();
|
await page.getByTestId('new-note-btn').click();
|
||||||
@@ -17,8 +11,7 @@ test.describe('Notes CRUD', () => {
|
|||||||
await page.getByTestId('close-editor-btn').click();
|
await page.getByTestId('close-editor-btn').click();
|
||||||
|
|
||||||
// Then the note is visible in the notes list
|
// Then the note is visible in the notes list
|
||||||
await expect(page.getByTestId('note-card')).toBeVisible();
|
await expect(noteCard(page, 'My First Note')).toBeVisible();
|
||||||
await expect(page.getByText('My First Note')).toBeVisible();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Scenario: Edited note title is reflected in the notes list', async ({ authenticatedPage: page }) => {
|
test('Scenario: Edited note title is reflected in the notes list', async ({ authenticatedPage: page }) => {
|
||||||
@@ -31,7 +24,7 @@ test.describe('Notes CRUD', () => {
|
|||||||
await page.getByTestId('close-editor-btn').click();
|
await page.getByTestId('close-editor-btn').click();
|
||||||
|
|
||||||
// When the user changes the title to "Updated Title"
|
// When the user changes the title to "Updated Title"
|
||||||
await page.getByTestId('note-card').click();
|
await noteCard(page, 'Original Title').click();
|
||||||
await page.getByTestId('note-title-input').clear();
|
await page.getByTestId('note-title-input').clear();
|
||||||
await page.getByTestId('note-title-input').fill('Updated Title');
|
await page.getByTestId('note-title-input').fill('Updated Title');
|
||||||
await page.getByTestId('close-editor-btn').click();
|
await page.getByTestId('close-editor-btn').click();
|
||||||
@@ -47,8 +40,9 @@ test.describe('Notes CRUD', () => {
|
|||||||
await page.getByTestId('close-editor-btn').click();
|
await page.getByTestId('close-editor-btn').click();
|
||||||
|
|
||||||
// When the user trashes the note
|
// When the user trashes the note
|
||||||
await page.getByTestId('note-card').hover();
|
const card = noteCard(page, 'Delete Me');
|
||||||
await page.getByTestId('trash-btn').click();
|
await card.hover();
|
||||||
|
await card.getByTestId('trash-btn').click();
|
||||||
|
|
||||||
// Then the note is no longer visible
|
// Then the note is no longer visible
|
||||||
await expect(page.getByText('Delete Me')).not.toBeVisible();
|
await expect(page.getByText('Delete Me')).not.toBeVisible();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { test, expect } from './helpers/fixtures.js';
|
import { test, expect, noteCard } from './helpers/fixtures.js';
|
||||||
|
|
||||||
test.describe('Offline Support', () => {
|
test.describe('Offline Support', () => {
|
||||||
test('Scenario: Sync status is visible to the user', async ({ authenticatedPage: page }) => {
|
test('Scenario: Sync status is visible to the user', async ({ authenticatedPage: page }) => {
|
||||||
@@ -28,7 +28,7 @@ test.describe('Offline Support', () => {
|
|||||||
await page.context().setOffline(true);
|
await page.context().setOffline(true);
|
||||||
|
|
||||||
// And the user edits the note title to "Offline Edited"
|
// And the user edits the note title to "Offline Edited"
|
||||||
await page.getByTestId('note-card').click();
|
await noteCard(page, 'Offline Edit').click();
|
||||||
await page.getByTestId('note-title-input').clear();
|
await page.getByTestId('note-title-input').clear();
|
||||||
await page.getByTestId('note-title-input').fill('Offline Edited');
|
await page.getByTestId('note-title-input').fill('Offline Edited');
|
||||||
await page.getByTestId('close-editor-btn').click();
|
await page.getByTestId('close-editor-btn').click();
|
||||||
@@ -107,8 +107,9 @@ test.describe('Offline Support', () => {
|
|||||||
await page.context().setOffline(true);
|
await page.context().setOffline(true);
|
||||||
|
|
||||||
// And the user trashes the note
|
// And the user trashes the note
|
||||||
await page.getByTestId('note-card').hover();
|
const card = noteCard(page, 'Offline Trash');
|
||||||
await page.getByTestId('trash-btn').click();
|
await card.hover();
|
||||||
|
await card.getByTestId('trash-btn').click();
|
||||||
|
|
||||||
// Then the note is no longer visible
|
// Then the note is no longer visible
|
||||||
await expect(page.getByText('Offline Trash')).not.toBeVisible();
|
await expect(page.getByText('Offline Trash')).not.toBeVisible();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { test, expect } from './helpers/fixtures.js';
|
import { test, expect, noteCard } from './helpers/fixtures.js';
|
||||||
|
|
||||||
test.describe('Organization Features', () => {
|
test.describe('Organization Features', () => {
|
||||||
test('Scenario: Pinned note appears under the Pinned section', async ({ authenticatedPage: page }) => {
|
test('Scenario: Pinned note appears under the Pinned section', async ({ authenticatedPage: page }) => {
|
||||||
@@ -8,8 +8,9 @@ test.describe('Organization Features', () => {
|
|||||||
await page.getByTestId('close-editor-btn').click();
|
await page.getByTestId('close-editor-btn').click();
|
||||||
|
|
||||||
// When the user pins the note
|
// When the user pins the note
|
||||||
await page.getByTestId('note-card').hover();
|
const pinCard = noteCard(page, 'Pin Me');
|
||||||
await page.getByTestId('pin-btn').click();
|
await pinCard.hover();
|
||||||
|
await pinCard.getByTestId('pin-btn').click();
|
||||||
|
|
||||||
// Then the "Pinned" section is visible
|
// Then the "Pinned" section is visible
|
||||||
await expect(page.getByText('Pinned')).toBeVisible();
|
await expect(page.getByText('Pinned')).toBeVisible();
|
||||||
@@ -22,8 +23,9 @@ test.describe('Organization Features', () => {
|
|||||||
await page.getByTestId('close-editor-btn').click();
|
await page.getByTestId('close-editor-btn').click();
|
||||||
|
|
||||||
// When the user archives the note
|
// When the user archives the note
|
||||||
await page.getByTestId('note-card').hover();
|
const archiveCard = noteCard(page, 'Archive Me');
|
||||||
await page.getByTestId('archive-btn').click();
|
await archiveCard.hover();
|
||||||
|
await archiveCard.getByTestId('archive-btn').click();
|
||||||
|
|
||||||
// Then the note is no longer visible in the main view
|
// Then the note is no longer visible in the main view
|
||||||
await expect(page.getByText('Archive Me')).not.toBeVisible();
|
await expect(page.getByText('Archive Me')).not.toBeVisible();
|
||||||
@@ -40,7 +42,7 @@ test.describe('Organization Features', () => {
|
|||||||
await page.getByTestId('close-editor-btn').click();
|
await page.getByTestId('close-editor-btn').click();
|
||||||
|
|
||||||
// Then the note is saved and visible in the notes list
|
// Then the note is saved and visible in the notes list
|
||||||
await expect(page.getByTestId('note-card')).toBeVisible();
|
await expect(noteCard(page, 'Colored Note')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Scenario: Filtering by tag shows only matching notes', async ({ authenticatedPage: page }) => {
|
test('Scenario: Filtering by tag shows only matching notes', async ({ authenticatedPage: page }) => {
|
||||||
@@ -57,7 +59,7 @@ test.describe('Organization Features', () => {
|
|||||||
await page.getByTestId('close-editor-btn').click();
|
await page.getByTestId('close-editor-btn').click();
|
||||||
|
|
||||||
// When the user filters by the #important tag
|
// When the user filters by the #important tag
|
||||||
const tagChip = page.getByTestId('tag-chip').filter({ hasText: '#important' });
|
const tagChip = page.getByTestId('tag-filter').getByTestId('tag-chip').filter({ hasText: '#important' });
|
||||||
if (await tagChip.isVisible()) {
|
if (await tagChip.isVisible()) {
|
||||||
await tagChip.click();
|
await tagChip.click();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { test, expect } from './helpers/fixtures.js';
|
import { test, expect, noteCard } from './helpers/fixtures.js';
|
||||||
|
|
||||||
test.describe('Search', () => {
|
test.describe('Search', () => {
|
||||||
test('Scenario: Searching by title returns the matching note', async ({ authenticatedPage: page }) => {
|
test('Scenario: Searching by title returns the matching note', async ({ authenticatedPage: page }) => {
|
||||||
@@ -42,6 +42,6 @@ test.describe('Search', () => {
|
|||||||
await page.waitForTimeout(500);
|
await page.waitForTimeout(500);
|
||||||
|
|
||||||
// Then no notes are displayed
|
// Then no notes are displayed
|
||||||
await expect(page.getByTestId('note-card')).not.toBeVisible();
|
await expect(page.getByTestId('note-card')).toHaveCount(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user