Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
{
'-rotate-90': !open,
},
'mr-2 transition-[transform]',
'mr-2 transition-transform',
)
"
/>
Expand Down Expand Up @@ -90,3 +90,11 @@ const handleTitleClick = () => {
open.value = !open.value;
};
</script>

<style scoped>
@reference "../index.css";

:deep(header button) {
@apply cursor-pointer;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import userEvent from '@testing-library/user-event';
import { screen } from '@testing-library/vue';
import { describe, expect, it } from 'vitest';
import { defineComponent } from 'vue';

import SbaButton from './sba-button.vue';

import { render } from '@/test-utils';

describe('SbaButton', () => {
it('renders as a button element by default', () => {
render(SbaButton, { slots: { default: 'Click me' } });
expect(
screen.getByRole('button', { name: 'Click me' }),
).toBeInTheDocument();
});

it('renders slot content', () => {
render(SbaButton, { slots: { default: 'Submit' } });
expect(screen.getByText('Submit')).toBeVisible();
});

it('renders as an anchor element when as="a"', () => {
render(SbaButton, {
props: { as: 'a', href: '#' },
slots: { default: 'Link' },
});
expect(screen.getByRole('link', { name: 'Link' })).toBeInTheDocument();
});

it('sets href on anchor element', () => {
render(SbaButton, {
props: { as: 'a', href: 'https://example.com' },
slots: { default: 'Link' },
});
expect(screen.getByRole('link', { name: 'Link' })).toHaveAttribute(
'href',
'https://example.com',
);
});

it('sets title attribute', () => {
render(SbaButton, {
props: { title: 'My tooltip' },
slots: { default: 'Btn' },
});
expect(screen.getByRole('button', { name: 'Btn' })).toHaveAttribute(
'title',
'My tooltip',
);
});

it('is disabled when disabled prop is true', () => {
render(SbaButton, {
props: { disabled: true },
slots: { default: 'Disabled' },
});
expect(screen.getByRole('button', { name: 'Disabled' })).toBeDisabled();
});

it('is not disabled by default', () => {
render(SbaButton, { slots: { default: 'Active' } });
expect(screen.getByRole('button', { name: 'Active' })).not.toBeDisabled();
});

it('emits click event when button is clicked', async () => {
const { emitted } = render(SbaButton, { slots: { default: 'Click' } });
await userEvent.click(screen.getByRole('button', { name: 'Click' }));
expect(emitted().click).toHaveLength(1);
});

it('does not emit click event when rendered as anchor', async () => {
const { emitted } = render(SbaButton, {
props: { as: 'a', href: '#' },
slots: { default: 'Link' },
});
await userEvent.click(screen.getByRole('link', { name: 'Link' }));
expect(emitted().click).toBeUndefined();
});

it('accepts a Vue component as the as prop and emits click', async () => {
const StubComponent = defineComponent({
template: '<button v-bind="$attrs"><slot /></button>',
});
const { emitted } = render(SbaButton, {
props: { as: StubComponent },
slots: { default: 'Component' },
});
await userEvent.click(screen.getByRole('button', { name: 'Component' }));
expect(emitted().click).toHaveLength(1);
});

it('passes attrs through to a component passed as the as prop', () => {
const StubComponent = defineComponent({
template: '<span v-bind="$attrs"><slot /></span>',
});
render(SbaButton, {
props: { as: StubComponent, primary: true },
slots: { default: 'Component' },
});
expect(screen.getByText('Component')).toHaveClass('is-primary');
});

it('applies is-primary class when primary prop is true', () => {
render(SbaButton, {
props: { primary: true },
slots: { default: 'Primary' },
});
expect(screen.getByRole('button', { name: 'Primary' })).toHaveClass(
'is-primary',
);
});

it('does not apply is-primary class by default', () => {
render(SbaButton, { slots: { default: 'Default' } });
expect(screen.getByRole('button', { name: 'Default' })).not.toHaveClass(
'is-primary',
);
});

it.each([
['2xs', 'px-1.5'],
['xs', 'px-2'],
['sm', 'px-3'],
['base', 'px-4'],
])('applies correct padding class for size="%s"', (size, expectedClass) => {
render(SbaButton, { props: { size }, slots: { default: 'Btn' } });
expect(screen.getByRole('button', { name: 'Btn' })).toHaveClass(
expectedClass,
);
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<component
:is="as"
class="btn relative items-center"
class="btn relative items-center cursor-pointer"
v-bind="componentAttrs"
@click="handleClick"
>
Expand All @@ -19,11 +19,8 @@ const props = defineProps({
default: '',
},
as: {
type: String,
type: [String, Object, Function],
default: 'button',
validator(value) {
return ['a', 'button'].includes(value);
},
},
href: {
type: String,
Expand All @@ -49,7 +46,7 @@ const attrs = useAttrs();

const cssClasses = computed(() => {
return {
'px-1 py-0 text-xs': props.size === '2xs',
'px-1.5 py-0.5 text-xs': props.size === '2xs',
'px-2 py-2 text-xs': props.size === 'xs',
'px-3 py-2': props.size === 'sm',
'px-4 py-3': props.size === 'base',
Expand Down Expand Up @@ -79,17 +76,16 @@ const componentAttrs = computed(() => {
type: props.type,
};
}
return {};
return common;
});

const emit = defineEmits(['click']);
const handleClick = (event) => {
if (props.as === 'button') {
emit('click', event);
}
if (props.as === 'a') {
event.stopPropagation();
return;
}
emit('click', event);
};
</script>

Expand Down
10 changes: 0 additions & 10 deletions spring-boot-admin-server-ui/src/main/frontend/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,6 @@ th {
@apply border-b border-gray-200;
}

.-rotate-90 {
--tw-rotate: -90deg;
transform: rotate(var(--tw-rotate));
}

.rotate-90 {
--tw-rotate: 90deg;
transform: rotate(var(--tw-rotate));
}

table.table-wide td {
@apply px-2 py-1.5;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,50 @@
<sba-button-group class="application-list-item__header__actions text-right">
<router-link v-slot="{ navigate }" :to="journalLink" custom>
<sba-button
:title="$t('applications.actions.journal')"
:size="size === 'xs' ? '2xs' : undefined"
:title="t('applications.actions.journal')"
@click.stop="navigate"
>
<font-awesome-icon :icon="faHistory" />
<font-awesome-icon :icon="faScroll" :size="size" />
</sba-button>
</router-link>
<sba-button
v-if="hasNotificationFiltersSupport"
:id="`nf-settings-${item.name || item.id}`"
:size="size === 'xs' ? '2xs' : undefined"
:title="$t('applications.actions.notification_filters')"
@click.stop="$emit('filter-settings', item)"
>
<font-awesome-icon
:size="size"
:icon="hasActiveNotificationFilter ? faBellSlash : faBell"
/>
</sba-button>
<sba-button
v-if="item.isUnregisterable"
class="btn-unregister"
:title="$t('applications.actions.unregister')"
:size="size === 'xs' ? '2xs' : undefined"
:title="t('applications.actions.unregister')"
@click.stop="actionHandler.unregister(item)"
>
<font-awesome-icon :icon="faTrash" />
<font-awesome-icon :size="size" :icon="faTrash" />
</sba-button>
<sba-button
v-if="item.hasEndpoint('restart')"
:title="$t('applications.actions.restart')"
:size="size === 'xs' ? '2xs' : undefined"
:title="t('applications.actions.restart')"
@click.stop="actionHandler.restart(item)"
>
<font-awesome-icon :icon="faUndoAlt" />
<font-awesome-icon :size="size" :icon="faUndoAlt" />
</sba-button>
<sba-button
v-if="item.hasEndpoint('shutdown')"
:title="$t('applications.actions.shutdown')"
:size="size === 'xs' ? '2xs' : undefined"
:title="t('applications.actions.shutdown')"
class="is-danger btn-shutdown"
@click.stop="actionHandler.shutdown(item)"
>
<font-awesome-icon :icon="faPowerOff" />
<font-awesome-icon :size="size" :icon="faPowerOff" />
</sba-button>
</sba-button-group>
</template>
Expand All @@ -48,16 +54,18 @@
import {
faBell,
faBellSlash,
faHistory,
faPowerOff,
faScroll,
faTrash,
faUndoAlt,
} from '@fortawesome/free-solid-svg-icons';
import { useNotificationCenter } from '@stekoe/vue-toast-notificationcenter';
import { inject } from 'vue';
import { PropType, inject } from 'vue';
import { useI18n } from 'vue-i18n';
import { RouteLocationNamedRaw } from 'vue-router';

import SbaButtonGroup from '@/components/sba-button-group.vue';

import Application from '@/services/application';
import Instance from '@/services/instance';
import {
Expand All @@ -83,6 +91,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
size: {
type: String as PropType<'xs' | 'sm'>,
default: 'sm',
},
});

defineEmits(['filter-settings']);
Expand Down Expand Up @@ -110,9 +122,4 @@ if (props.item instanceof Application) {
.application-list-item__header__actions {
@apply hidden lg:inline-flex p-1 bg-black/5 rounded-lg;
}

.btn-shutdown,
.btn-unregister {
@apply ml-1;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
v-for="instance in instances"
:key="instance.id"
:data-testid="instance.id"
class="flex p-2 pr-4 hover:bg-gray-100 gap-2 odd:bg-gray-50 items-center"
class="flex p-2 pr-4 hover:bg-gray-100 hover:cursor-pointer gap-2 odd:bg-gray-50 items-center"
@click.stop="showDetails(instance)"
>
<div class="pt-1 md:w-16 text-center">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
},
"instances": {
"shutdown": "Möchten Sie die Instanz <code>{name}</code> herunterfahren?",
"open_details": "Instanzdetails öffnen",
"restart": "Möchten Sie die Instanz <code>{name}</code> neu starten?",
"restarted": "Die Instanz {name} wurde neu gestartet.",
"unregister": "Möchten Sie die Instanz <code>{name}</code> deregistrieren?",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"shutdown": "Shutdown instance <code>{name}</code>?",
"shutdown_successful": "Successfully shutdown instances {name}.",
"shutdown_failed": "Failed to shutdown instances {name}.",
"open_details": "Open instance details",
"restart": "Restart instance <code>{name}</code>?",
"restarted": "Successfully restarted instance {name}.",
"unregister": "Deregister instance <code>{name}</code>?",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"instances": {
"shutdown": "Detener instancia <code>{name}</code>?",
"open_details": "Abrir detalles de la instancia",
"restart": "Reinciar instancia <code>{name}</code>?",
"restarted": "Instancia reiniciada exitosamente"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"instances": {
"shutdown": "Shutdown instance <code>{name}</code>?",
"open_details": "Ouvrir les détails de l'instance",
"restart": "Restart instance <code>{name}</code>?",
"restarted": "Successfully restarted instance"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"instances": {
"shutdown": "Shutdown instance <code>{name}</code>?",
"open_details": "Opna upplýsingar um eintak",
"restart": "Restart instance <code>{name}</code>?",
"restarted": "Successfully restarted instance"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"shutdown": "<code>{name}</code> 인스턴스를 종료할까요?",
"shutdown_successful": "{name} 인스턴스를 종료하였습니다.",
"shutdown_failed": "{name} 인스턴스를 종료하지 못하였습니다.",
"open_details": "인스턴스 상세 정보 열기",
"restart": "<code>{name}</code> 인스턴스를 재시작할까요?",
"restarted": "{name} 인스턴스를 재시작하였습니다.",
"unregister": "<code>{name}</code> 인스턴스 등록을 해제할까요?",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"instances": {
"shutdown": "Shutdown instance <code>{name}</code>?",
"open_details": "Abrir detalhes da instância",
"restart": "Restart instance <code>{name}</code>?",
"restarted": "Successfully restarted instance"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"shutdown": "Остановить экземпляр <code>{name}</code>?",
"shutdown_successful": "Экземпляр {name} успешно остановлен.",
"shutdown_failed": "Ошибка остановки экземпляра {name}.",
"open_details": "Открыть сведения об экземпляре",
"restart": "Перезапустить экземпляр <code>{name}</code>?",
"restarted": "Экземпляр {name} успешно перезапущен.",
"unregister": "Удалить регистрацию экземпляра <code>{name}</code>?",
Expand Down
Loading