Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 29 additions & 109 deletions ui/cypress/e2e/admin.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,125 +6,45 @@ describe('admin', () => {
it('Create new user', () => {
cy.visit('/')

cy.get('.hidden > :nth-child(1)').click();

cy.wait(100);

cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').clear('ad');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('admin@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('admin');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();

cy.wait(100);

cy.get('.text').click();
cy.get('p.text-success').click();
cy.get('thead > tr > .text-end > .btn').click();

cy.wait(100);

cy.get('#email').clear('te');
cy.get('#email').type('test@test.nl');
cy.get('#password').clear();
cy.get('#password').type('Testing1');
cy.get('.modal-action > .btn').click();

cy.wait(100);

cy.get(':nth-child(3) > summary.btn').click();
cy.wait(50);
cy.get('.menu > :nth-child(3) > .btn').click();
cy.wait(50);
cy.get('.hidden > :nth-child(1)').click();
cy.wait(50);

cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('test@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('Testing1');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();

cy.wait(50);

cy.login("admin@test.nl", "admin")
cy.navigateToAdminPanel()
cy.createUser("user@test.nl", "Testing1", false)
cy.logout()
cy.login("user@test.nl", "Testing1")
cy.url().should('include', '/home')
})

it('Create another admin', function() {
cy.visit('/');
cy.get('.hidden > :nth-child(1)').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('admin@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('admin');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();
cy.get('.text').click();
cy.get('p.text-success').click();
cy.get('thead > tr > .text-end').click();
cy.get('.modal-box').click();
cy.get('#email').clear('ad');
cy.get('#email').type('admin2@test.nl');
cy.get('#password').clear();
cy.get('#password').type('Testing1');
cy.get('.checkbox').check();
cy.get('.modal-action > .btn').click();
cy.get('.text').click();
cy.get('.menu > :nth-child(3) > .btn').click();
cy.get('.hidden > :nth-child(1)').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('admin2@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('Testing1');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();
cy.get('.text').click();
cy.get(':nth-child(2) > .btn > .fa-solid').click();
cy.visit('/')

cy.login("admin@test.nl", "admin")
cy.navigateToAdminPanel()
cy.createUser("another@admin.nl", "AdminAwesome1", true)
cy.logout()
cy.login("another@admin.nl", "AdminAwesome1")
cy.navigateToAdminPanel()
cy.url().should('include', '/admin-panel')
});

it('Delete user', function() {
cy.visit('/');
cy.get('.hidden > :nth-child(1)').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').clear('ad');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('admin@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('admin');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();
cy.get('.text').click();
cy.get('p.text-success').click();
cy.get('thead > tr > .text-end > .btn > .fa-solid').click();
cy.get('#email').clear('te');
cy.get('#email').type('test@test.nl');
cy.get('#password').clear();
cy.get('#password').type('Testing1');
cy.get('.modal-action > .btn').click();
cy.get('.text').click();
cy.get('.menu > :nth-child(3) > .btn').click();
cy.get('.hidden > :nth-child(1)').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').clear('te');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('test@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('Testing1');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();
cy.get('.text').click();
cy.get('[open=""] > .menu > :nth-child(2) > .btn').click();
cy.get('.hidden > :nth-child(1)').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').clear('ad');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('admin@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('admin');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();
cy.get('.text').click();
cy.get('p.text-success').click();
cy.get(':nth-child(2) > .text-end > .btn > .fa-solid').click();
cy.get('.btn-error').click();
cy.get('.text').click();
cy.get('.menu > :nth-child(3) > .btn > .fa-solid').click();
cy.get('.hidden > :nth-child(1)').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').clear('te');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('test@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('Testing1');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();

cy.get('.alert span').should('contain.text', 'Could not login with the given email and password');
cy.login("admin@test.nl", "admin")
cy.navigateToAdminPanel()
cy.createUser("user@test.nl", "Testing1", false)
cy.logout()

cy.login("user@test.nl", "Testing1")
cy.url().should('include', '/home')
cy.logout()

cy.login("admin@test.nl", "admin")
cy.navigateToAdminPanel()

cy.deleteUser("user@test.nl")
cy.logout()

cy.login("user@test.nl", "Testing1")
cy.get('[data-cy="login-error"]').should('contain.text', 'Could not login with the given email and password');
});
})
32 changes: 31 additions & 1 deletion ui/cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,34 @@
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

Cypress.Commands.addAll({
login(email, password) {
cy.get('[data-cy="open-login-modal"]').click();
cy.get('[data-cy="login-email"]').type(email);
cy.get('[data-cy="login-password"]').type(password);
cy.get('[data-cy="login-button"]').click();
},
logout() {
cy.get('[data-cy="toggle-profile-menu-dropdown"').click();
Copy link

Copilot AI Apr 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The selector is missing a closing bracket. It should be corrected to: cy.get('[data-cy="toggle-profile-menu-dropdown"]').click();

Suggested change
cy.get('[data-cy="toggle-profile-menu-dropdown"').click();
cy.get('[data-cy="toggle-profile-menu-dropdown"]').click();

Copilot uses AI. Check for mistakes.
cy.get('[data-cy="logout"]').click();
},
navigateToAdminPanel() {
cy.get('[data-cy="toggle-profile-menu-dropdown"').click();
cy.get('[data-cy="admin-panel"]').click();
},
createUser(email, password, isAdmin) {
cy.get('[data-cy="create-new-user"]').click();
cy.get('[data-cy="new-user-email"]').type(email);
cy.get('[data-cy="new-user-password"]').type(password);
if (isAdmin) {
cy.get('[data-cy="new-user-admin"]').check();
}
cy.get('[data-cy="submit-new-user"]').click();
},
deleteUser(email) {
cy.get(`[data-cy="delete-user-${email}"]`).click();
cy.get(`[data-cy="delete-user"]`).click();
}
})
7 changes: 5 additions & 2 deletions ui/src/components/Alert.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { JSXElement } from 'solid-js'
import { JSXElement, JSX } from 'solid-js'
import { clsx } from 'clsx'
import { TranslationKey, useLocale } from '../context/LocaleProvider'

Expand All @@ -8,7 +8,9 @@ interface AlertProps {
class?: string
}

export function Alert(props: AlertProps): JSXElement {
export function Alert(
props: AlertProps & JSX.HTMLAttributes<HTMLDivElement>
): JSXElement {
const { t } = useLocale()

const translated = () => t((props.message as TranslationKey) ?? '')
Expand All @@ -25,6 +27,7 @@ export function Alert(props: AlertProps): JSXElement {
<div
role="alert"
class={clsx('alert my-4', types[props.type].alert, props?.class)}
{...props}
Comment on lines 27 to +30
Copy link

Copilot AI Apr 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Spreading {...props} after setting the 'class' attribute may override the computed class names. Consider reordering the props spread or merging the classes to preserve the intended styling.

Suggested change
class={clsx('alert my-4', types[props.type].alert, props?.class)}
{...props}
{...props}
class={clsx('alert my-4', types[props.type].alert, props?.class)}

Copilot uses AI. Check for mistakes.
>
<i class={clsx('fa-solid mr-2', types[props.type].icon)} />
<span>{message()}</span>
Expand Down
2 changes: 2 additions & 0 deletions ui/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function Button(props: {
size?: DaisyUIButtonSize
style?: DaisyUIButtonStyle
class?: string
dataCy?: string
}): JSXElement {
return (
<button
Expand All @@ -50,6 +51,7 @@ export function Button(props: {
)}
type={props.type ?? 'button'}
onClick={(event) => props.onClick?.(event)}
data-cy={props.dataCy}
>
<Show when={props.icon}>
<i class={props.icon} />
Expand Down
9 changes: 8 additions & 1 deletion ui/src/components/LoginModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export function LoginModal(props: ModalBaseProps): JSXElement {
placeholder={t('email_placeholder')}
icon={<i class="fa-solid fa-envelope" />}
disabled={at2FAStep()}
data-cy="login-email"
/>
)}
</Login.Field>
Expand All @@ -114,12 +115,17 @@ export function LoginModal(props: ModalBaseProps): JSXElement {
placeholder={t('password')}
icon={<i class="fa-solid fa-key" />}
disabled={at2FAStep()}
data-cy="login-password"
/>
)}
</Login.Field>

<Show when={loginState.response.status === 'error'}>
<Alert type="error" message={loginState.response.message} />
<Alert
type="error"
message={loginState.response.message}
data-cy="login-error"
/>
</Show>

<Show when={!at2FAStep()}>
Expand All @@ -137,6 +143,7 @@ export function LoginModal(props: ModalBaseProps): JSXElement {
color="primary"
class="w-full"
isLoading={loginState.submitting}
dataCy="login-button"
/>
</div>
</Show>
Expand Down
15 changes: 12 additions & 3 deletions ui/src/components/ProfileMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,30 @@ export function ProfileMenu(): JSXElement {

return (
<details class="dropdown dropdown-end">
<summary class="btn btn-ghost">
<summary class="btn btn-ghost" data-cy="toggle-profile-menu-dropdown">
<span class="text">{user()?.email}</span>
<i class="fa-solid fa-ellipsis" />
</summary>

<ul class="menu dropdown-content bg-base-200 w-40 rounded-box z-100">
<li>
<A class="btn btn-ghost justify-start" href="/account">
<A
class="btn btn-ghost justify-start"
href="/account"
data-cy="user-account"
>
<i class="fa-regular fa-address-card" />
{t('account')}
</A>
</li>

<Show when={user()?.isAdmin}>
<li>
<A class="btn btn-ghost justify-start" href="/admin-panel">
<A
class="btn btn-ghost justify-start"
href="/admin-panel"
data-cy="admin-panel"
>
<i class="fa-solid fa-screwdriver-wrench text-success" />
<p class="text-success">{t('admin_panel')}</p>
</A>
Expand All @@ -65,6 +73,7 @@ export function ProfileMenu(): JSXElement {
loggingOut() && 'btn-disabled'
)}
onClick={onLogout}
data-cy="logout"
>
<Show
when={loggingOut()}
Expand Down
4 changes: 4 additions & 0 deletions ui/src/components/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,15 @@ export function TopBar(): JSXElement {
<button
class="btn btn-ghost"
onClick={() => setOpenLoginModal(true)}
data-cy="open-login-modal"
>
{t('login')}
</button>

<button
class="btn btn-ghost"
onClick={() => setOpenRegisterModal(true)}
data-cy="open-register-modal"
>
{t('register')}
</button>
Expand Down Expand Up @@ -108,6 +110,7 @@ export function TopBar(): JSXElement {
setOpenLoginModal(true)
setSidebarOpen(false)
}}
data-cy="open-login-modal-mobile"
>
<i class="fa-solid fa-sign-in-alt mr-2" />
{t('login')}
Expand All @@ -119,6 +122,7 @@ export function TopBar(): JSXElement {
setOpenRegisterModal(true)
setSidebarOpen(false)
}}
data-cy="open-login-modal-mobile"
Copy link

Copilot AI Apr 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same data-cy value 'open-login-modal-mobile' is used for both the login and register mobile buttons, which may cause ambiguity in tests. Consider using a unique identifier for each button.

Suggested change
data-cy="open-login-modal-mobile"
data-cy="open-register-modal-mobile"

Copilot uses AI. Check for mistakes.
>
<i class="fa-solid fa-user-plus mr-2" />
{t('register')}
Expand Down
7 changes: 7 additions & 0 deletions ui/src/pages/admin_page/UsersAdmin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export function UsersAdmin(): JSXElement {
<button
class="btn btn-ghost btn-sm mx-1"
onClick={() => handleDelete(props.user)}
data-cy={`delete-user-${props.user.email}`}
>
<i class="fa-solid fa-trash text-error" />
</button>
Expand All @@ -110,6 +111,7 @@ export function UsersAdmin(): JSXElement {
<button
class="btn btn-primary btn-sm"
onClick={() => openModal('createUser')}
data-cy="create-new-user"
>
<i class="fa-solid fa-plus" />
<p class="hidden md:block">{t('create_new_user')}</p>
Expand Down Expand Up @@ -219,6 +221,7 @@ function DeleteUserModal(
type="submit"
color="error"
isLoading={state.submitting}
dataCy="delete-user"
/>
</div>
</Form>
Expand Down Expand Up @@ -271,6 +274,7 @@ function CreateUserModal(props: CreateUserModalProps): JSXElement {
error={field.error}
placeholder={t('email_placeholder')}
icon={<i class="fa-solid fa-envelope" />}
data-cy="new-user-email"
/>
)}
</Field>
Expand All @@ -293,6 +297,7 @@ function CreateUserModal(props: CreateUserModalProps): JSXElement {
error={field.error}
placeholder={t('password')}
icon={<i class="fa-solid fa-key" />}
data-cy="new-user-password"
/>
)}
</Field>
Expand All @@ -305,6 +310,7 @@ function CreateUserModal(props: CreateUserModalProps): JSXElement {
value={field.value ?? false}
error={field.error}
label={t('make_this_user_an_admin')}
data-cy="new-user-admin"
/>
)}
</Field>
Expand All @@ -316,6 +322,7 @@ function CreateUserModal(props: CreateUserModalProps): JSXElement {
color="primary"
class="mt-4 w-full"
isLoading={state.submitting}
dataCy="submit-new-user"
/>
</div>
</Form>
Expand Down