* feat: add initial files and spec 1 * feat: add spec for rich text feature * chore: restore file from broken AI session * feat: add project constraints to ai * chore: update spec feature prompt * feat: add multiple tags support - wip * test: fix test cases * chore: move tag list to below the inputs * chore: add ngrok files and updates * fix: session issues and query using HQL * test: fix test cases
274 lines
7.7 KiB
TypeScript
274 lines
7.7 KiB
TypeScript
/**
|
|
* E2E tests for Notes Management: Create, Update, Delete, and Share notes.
|
|
*/
|
|
|
|
const mockUser = {
|
|
userId: 1,
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
admin: false,
|
|
createdAt: '2026-01-01T00:00:00.000Z',
|
|
gravatarImageUrl: '',
|
|
lang: 'en'
|
|
};
|
|
|
|
const mockNote = {
|
|
id: 1,
|
|
title: 'Meeting notes',
|
|
description: 'Discuss quarterly goals',
|
|
url: null,
|
|
tags: ['work'],
|
|
lastUpdate: '2026-05-01',
|
|
shared: false,
|
|
shareToken: null
|
|
};
|
|
|
|
describe('Notes Management', () => {
|
|
/**
|
|
* Sets up authentication intercepts and visits the given path.
|
|
*
|
|
* @param {string} path - The path to visit.
|
|
*/
|
|
const visitWithAuth = (path: string): void => {
|
|
cy.intercept('GET', /\/rest\/user-sessions\/refresh/, {
|
|
statusCode: 200,
|
|
body: { token: 'fake-jwt-token', ...mockUser }
|
|
}).as('refreshToken');
|
|
|
|
cy.intercept('GET', /\/rest\/users\/me/, {
|
|
statusCode: 200,
|
|
body: mockUser
|
|
}).as('getCurrentUser');
|
|
|
|
cy.intercept('GET', /\/rest\/home\/tasks\/tags/, {
|
|
statusCode: 200,
|
|
body: ['work', 'personal']
|
|
}).as('getTags');
|
|
|
|
cy.visit(path, {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('TASKNOTE-TOKEN', 'fake-jwt-token');
|
|
win.localStorage.setItem('TASKNOTE-USER', JSON.stringify(mockUser));
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Create note
|
|
*/
|
|
describe('Create', () => {
|
|
beforeEach(() => {
|
|
visitWithAuth('/notes/new');
|
|
});
|
|
|
|
it('displays the add note form', () => {
|
|
cy.contains('Add Note').should('be.visible');
|
|
cy.get('input[name="note_title"]').should('be.visible');
|
|
cy.get('textarea[name="note_description"]').should('be.visible');
|
|
cy.contains('button', 'Save note').should('be.visible');
|
|
});
|
|
|
|
it('shows a validation error when submitted without a title', () => {
|
|
cy.contains('button', 'Save note').click();
|
|
|
|
cy.get('.alert-danger').should('be.visible');
|
|
});
|
|
|
|
it('shows a validation error when submitted without content', () => {
|
|
cy.get('input[name="note_title"]').type('A note title');
|
|
cy.contains('button', 'Save note').click();
|
|
|
|
cy.get('.alert-danger').should('be.visible');
|
|
});
|
|
|
|
it('creates a note and navigates to home on success', () => {
|
|
cy.intercept('POST', /\/rest\/notes/, {
|
|
statusCode: 201,
|
|
body: {
|
|
id: 10,
|
|
title: 'New note',
|
|
description: 'Some content',
|
|
url: null,
|
|
tags: [],
|
|
lastUpdate: '',
|
|
shared: false,
|
|
shareToken: null
|
|
}
|
|
}).as('createNote');
|
|
|
|
cy.intercept('GET', /\/rest\/tasks$/, { statusCode: 200, body: [] }).as('getTasks');
|
|
cy.intercept('GET', /\/rest\/notes$/, { statusCode: 200, body: [] }).as('getNotes');
|
|
|
|
cy.get('input[name="note_title"]').type('New note');
|
|
cy.get('textarea[name="note_description"]').type('Some content');
|
|
cy.contains('button', 'Save note').click();
|
|
|
|
cy.wait('@createNote');
|
|
cy.url().should('include', '/home');
|
|
});
|
|
|
|
it('shows an error alert when the API returns an error on create', () => {
|
|
cy.intercept('POST', /\/rest\/notes/, {
|
|
statusCode: 500,
|
|
body: { message: 'Internal Server Error' }
|
|
}).as('createNoteFail');
|
|
|
|
cy.get('input[name="note_title"]').type('Failing note');
|
|
cy.get('textarea[name="note_description"]').type('Some content');
|
|
cy.contains('button', 'Save note').click();
|
|
|
|
cy.wait('@createNoteFail');
|
|
cy.get('.alert-danger').should('be.visible');
|
|
});
|
|
|
|
it('navigates back to home when Cancel is clicked', () => {
|
|
cy.intercept('GET', /\/rest\/tasks$/, { statusCode: 200, body: [] }).as('getTasks');
|
|
cy.intercept('GET', /\/rest\/notes$/, { statusCode: 200, body: [] }).as('getNotes');
|
|
|
|
cy.contains('button', 'Cancel').click();
|
|
|
|
cy.url().should('include', '/home');
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Update note
|
|
*/
|
|
describe('Update', () => {
|
|
beforeEach(() => {
|
|
cy.intercept('GET', /\/rest\/notes\/\d+/, {
|
|
statusCode: 200,
|
|
body: mockNote
|
|
}).as('getNote');
|
|
|
|
visitWithAuth('/notes/edit/1');
|
|
cy.wait('@getNote');
|
|
});
|
|
|
|
it('pre-fills the form with the existing note data', () => {
|
|
cy.get('input[name="note_title"]').should('have.value', 'Meeting notes');
|
|
cy.get('textarea[name="note_description"]').should('have.value', 'Discuss quarterly goals');
|
|
});
|
|
|
|
it('updates the note and navigates to home on success', () => {
|
|
cy.intercept('PATCH', /\/rest\/notes\/\d+/, {
|
|
statusCode: 200,
|
|
body: { ...mockNote, title: 'Updated meeting notes' }
|
|
}).as('updateNote');
|
|
|
|
cy.intercept('GET', /\/rest\/tasks$/, { statusCode: 200, body: [] }).as('getTasks');
|
|
cy.intercept('GET', /\/rest\/notes$/, { statusCode: 200, body: [] }).as('getNotes');
|
|
|
|
cy.get('input[name="note_title"]').clear().type('Updated meeting notes');
|
|
cy.contains('button', 'Save note').click();
|
|
|
|
cy.wait('@updateNote');
|
|
cy.url().should('include', '/home');
|
|
});
|
|
|
|
it('shows an error alert when the update API call fails', () => {
|
|
cy.intercept('PATCH', /\/rest\/notes\/\d+/, {
|
|
statusCode: 500,
|
|
body: { message: 'Internal Server Error' }
|
|
}).as('updateNoteFail');
|
|
|
|
cy.get('input[name="note_title"]').clear().type('Updated note');
|
|
cy.contains('button', 'Save note').click();
|
|
|
|
cy.wait('@updateNoteFail');
|
|
cy.get('.alert-danger').should('be.visible');
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Delete note
|
|
*/
|
|
describe('Delete', () => {
|
|
beforeEach(() => {
|
|
cy.intercept('GET', /\/rest\/tasks$/, {
|
|
statusCode: 200,
|
|
body: []
|
|
}).as('getTasks');
|
|
|
|
cy.intercept('GET', /\/rest\/notes$/, {
|
|
statusCode: 200,
|
|
body: [mockNote]
|
|
}).as('getNotes');
|
|
|
|
visitWithAuth('/home');
|
|
cy.wait('@getNotes');
|
|
});
|
|
|
|
it('deletes a note via the note dropdown menu', () => {
|
|
cy.intercept('DELETE', /\/rest\/notes\/\d+/, {
|
|
statusCode: 204
|
|
}).as('deleteNote');
|
|
|
|
cy.get('[data-testid="note-dropdown-menu-1"]').click();
|
|
cy.get('[data-testid="note-dropdown-delete-item-1"]').click();
|
|
|
|
cy.wait('@deleteNote');
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Share note
|
|
*/
|
|
describe('Share', () => {
|
|
beforeEach(() => {
|
|
cy.intercept('GET', /\/rest\/tasks$/, {
|
|
statusCode: 200,
|
|
body: []
|
|
}).as('getTasks');
|
|
|
|
cy.intercept('GET', /\/rest\/notes$/, {
|
|
statusCode: 200,
|
|
body: [mockNote]
|
|
}).as('getNotes');
|
|
|
|
visitWithAuth('/home');
|
|
cy.wait('@getNotes');
|
|
});
|
|
|
|
it('shares a note via the note dropdown menu', () => {
|
|
cy.intercept('PUT', /\/rest\/notes\/\d+\/share/, {
|
|
statusCode: 200,
|
|
body: { ...mockNote, shared: true, shareToken: 'abc123' }
|
|
}).as('shareNote');
|
|
|
|
cy.get('[data-testid="note-dropdown-menu-1"]').click();
|
|
cy.get('[data-testid="note-dropdown-share-item-1"]').click();
|
|
|
|
cy.wait('@shareNote');
|
|
});
|
|
|
|
it('unshares a shared note via the note dropdown menu', () => {
|
|
const sharedNote = { ...mockNote, shared: true, shareToken: 'abc123' };
|
|
|
|
cy.intercept('GET', /\/rest\/notes$/, {
|
|
statusCode: 200,
|
|
body: [sharedNote]
|
|
}).as('getSharedNotes');
|
|
|
|
cy.intercept('PUT', /\/rest\/notes\/\d+\/unshare/, {
|
|
statusCode: 200,
|
|
body: { ...sharedNote, shared: false, shareToken: null }
|
|
}).as('unshareNote');
|
|
|
|
cy.visit('/home', {
|
|
onBeforeLoad(win) {
|
|
win.localStorage.setItem('TASKNOTE-TOKEN', 'fake-jwt-token');
|
|
win.localStorage.setItem('TASKNOTE-USER', JSON.stringify(mockUser));
|
|
}
|
|
});
|
|
|
|
cy.wait('@getSharedNotes');
|
|
|
|
cy.get('[data-testid="note-dropdown-menu-1"]').click();
|
|
cy.get('[data-testid="note-dropdown-share-item-1"]').click();
|
|
|
|
cy.wait('@unshareNote');
|
|
});
|
|
});
|
|
});
|