Skip to content

Commit b871cb5

Browse files
committed
style: enhance button component and add translations for instance details
1 parent 2ba927f commit b871cb5

16 files changed

Lines changed: 177 additions & 23 deletions

File tree

spring-boot-admin-server-ui/src/main/frontend/components/sba-accordion.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,11 @@ const handleTitleClick = () => {
9090
open.value = !open.value;
9191
};
9292
</script>
93+
94+
<style scoped>
95+
@reference "../index.css";
96+
97+
:deep(header button) {
98+
@apply cursor-pointer;
99+
}
100+
</style>
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import userEvent from '@testing-library/user-event';
2+
import { screen } from '@testing-library/vue';
3+
import { describe, expect, it } from 'vitest';
4+
import { defineComponent } from 'vue';
5+
6+
import SbaButton from './sba-button.vue';
7+
8+
import { render } from '@/test-utils';
9+
10+
describe('SbaButton', () => {
11+
it('renders as a button element by default', () => {
12+
render(SbaButton, { slots: { default: 'Click me' } });
13+
expect(
14+
screen.getByRole('button', { name: 'Click me' }),
15+
).toBeInTheDocument();
16+
});
17+
18+
it('renders slot content', () => {
19+
render(SbaButton, { slots: { default: 'Submit' } });
20+
expect(screen.getByText('Submit')).toBeVisible();
21+
});
22+
23+
it('renders as an anchor element when as="a"', () => {
24+
render(SbaButton, {
25+
props: { as: 'a', href: '#' },
26+
slots: { default: 'Link' },
27+
});
28+
expect(screen.getByRole('link', { name: 'Link' })).toBeInTheDocument();
29+
});
30+
31+
it('sets href on anchor element', () => {
32+
render(SbaButton, {
33+
props: { as: 'a', href: 'https://example.com' },
34+
slots: { default: 'Link' },
35+
});
36+
expect(screen.getByRole('link', { name: 'Link' })).toHaveAttribute(
37+
'href',
38+
'https://example.com',
39+
);
40+
});
41+
42+
it('sets title attribute', () => {
43+
render(SbaButton, {
44+
props: { title: 'My tooltip' },
45+
slots: { default: 'Btn' },
46+
});
47+
expect(screen.getByRole('button', { name: 'Btn' })).toHaveAttribute(
48+
'title',
49+
'My tooltip',
50+
);
51+
});
52+
53+
it('is disabled when disabled prop is true', () => {
54+
render(SbaButton, {
55+
props: { disabled: true },
56+
slots: { default: 'Disabled' },
57+
});
58+
expect(screen.getByRole('button', { name: 'Disabled' })).toBeDisabled();
59+
});
60+
61+
it('is not disabled by default', () => {
62+
render(SbaButton, { slots: { default: 'Active' } });
63+
expect(screen.getByRole('button', { name: 'Active' })).not.toBeDisabled();
64+
});
65+
66+
it('emits click event when button is clicked', async () => {
67+
const { emitted } = render(SbaButton, { slots: { default: 'Click' } });
68+
await userEvent.click(screen.getByRole('button', { name: 'Click' }));
69+
expect(emitted().click).toHaveLength(1);
70+
});
71+
72+
it('does not emit click event when rendered as anchor', async () => {
73+
const { emitted } = render(SbaButton, {
74+
props: { as: 'a', href: '#' },
75+
slots: { default: 'Link' },
76+
});
77+
await userEvent.click(screen.getByRole('link', { name: 'Link' }));
78+
expect(emitted().click).toBeUndefined();
79+
});
80+
81+
it('accepts a Vue component as the as prop and emits click', async () => {
82+
const StubComponent = defineComponent({
83+
template: '<button v-bind="$attrs"><slot /></button>',
84+
});
85+
const { emitted } = render(SbaButton, {
86+
props: { as: StubComponent },
87+
slots: { default: 'Component' },
88+
});
89+
await userEvent.click(screen.getByRole('button', { name: 'Component' }));
90+
expect(emitted().click).toHaveLength(1);
91+
});
92+
93+
it('passes attrs through to a component passed as the as prop', () => {
94+
const StubComponent = defineComponent({
95+
template: '<span v-bind="$attrs"><slot /></span>',
96+
});
97+
render(SbaButton, {
98+
props: { as: StubComponent, primary: true },
99+
slots: { default: 'Component' },
100+
});
101+
expect(screen.getByText('Component')).toHaveClass('is-primary');
102+
});
103+
104+
it('applies is-primary class when primary prop is true', () => {
105+
render(SbaButton, {
106+
props: { primary: true },
107+
slots: { default: 'Primary' },
108+
});
109+
expect(screen.getByRole('button', { name: 'Primary' })).toHaveClass(
110+
'is-primary',
111+
);
112+
});
113+
114+
it('does not apply is-primary class by default', () => {
115+
render(SbaButton, { slots: { default: 'Default' } });
116+
expect(screen.getByRole('button', { name: 'Default' })).not.toHaveClass(
117+
'is-primary',
118+
);
119+
});
120+
121+
it.each([
122+
['2xs', 'px-1.5'],
123+
['xs', 'px-2'],
124+
['sm', 'px-3'],
125+
['base', 'px-4'],
126+
])('applies correct padding class for size="%s"', (size, expectedClass) => {
127+
render(SbaButton, { props: { size }, slots: { default: 'Btn' } });
128+
expect(screen.getByRole('button', { name: 'Btn' })).toHaveClass(
129+
expectedClass,
130+
);
131+
});
132+
});

spring-boot-admin-server-ui/src/main/frontend/components/sba-button.vue

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,8 @@ const props = defineProps({
1919
default: '',
2020
},
2121
as: {
22-
type: String,
22+
type: [String, Object, Function],
2323
default: 'button',
24-
validator(value) {
25-
return ['a', 'button'].includes(value);
26-
},
2724
},
2825
href: {
2926
type: String,
@@ -79,17 +76,16 @@ const componentAttrs = computed(() => {
7976
type: props.type,
8077
};
8178
}
82-
return {};
79+
return common;
8380
});
8481
8582
const emit = defineEmits(['click']);
8683
const handleClick = (event) => {
87-
if (props.as === 'button') {
88-
emit('click', event);
89-
}
9084
if (props.as === 'a') {
9185
event.stopPropagation();
86+
return;
9287
}
88+
emit('click', event);
9389
};
9490
</script>
9591

spring-boot-admin-server-ui/src/main/frontend/views/applications/ApplicationListItemAction.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
import {
5555
faBell,
5656
faBellSlash,
57-
faHistory,
5857
faPowerOff,
5958
faScroll,
6059
faTrash,

spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
},
4141
"instances": {
4242
"shutdown": "Möchten Sie die Instanz <code>{name}</code> herunterfahren?",
43+
"open_details": "Instanzdetails öffnen",
4344
"restart": "Möchten Sie die Instanz <code>{name}</code> neu starten?",
4445
"restarted": "Die Instanz {name} wurde neu gestartet.",
4546
"unregister": "Möchten Sie die Instanz <code>{name}</code> deregistrieren?",

spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"shutdown": "Shutdown instance <code>{name}</code>?",
4747
"shutdown_successful": "Successfully shutdown instances {name}.",
4848
"shutdown_failed": "Failed to shutdown instances {name}.",
49+
"open_details": "Open instance details",
4950
"restart": "Restart instance <code>{name}</code>?",
5051
"restarted": "Successfully restarted instance {name}.",
5152
"unregister": "Deregister instance <code>{name}</code>?",

spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.es.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
},
2020
"instances": {
2121
"shutdown": "Detener instancia <code>{name}</code>?",
22+
"open_details": "Abrir detalles de la instancia",
2223
"restart": "Reinciar instancia <code>{name}</code>?",
2324
"restarted": "Instancia reiniciada exitosamente"
2425
}

spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.fr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
},
2020
"instances": {
2121
"shutdown": "Shutdown instance <code>{name}</code>?",
22+
"open_details": "Ouvrir les détails de l'instance",
2223
"restart": "Restart instance <code>{name}</code>?",
2324
"restarted": "Successfully restarted instance"
2425
}

spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.is.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
},
2020
"instances": {
2121
"shutdown": "Shutdown instance <code>{name}</code>?",
22+
"open_details": "Opna upplýsingar um eintak",
2223
"restart": "Restart instance <code>{name}</code>?",
2324
"restarted": "Successfully restarted instance"
2425
}

spring-boot-admin-server-ui/src/main/frontend/views/applications/i18n.ko.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"shutdown": "<code>{name}</code> 인스턴스를 종료할까요?",
4444
"shutdown_successful": "{name} 인스턴스를 종료하였습니다.",
4545
"shutdown_failed": "{name} 인스턴스를 종료하지 못하였습니다.",
46+
"open_details": "인스턴스 상세 정보 열기",
4647
"restart": "<code>{name}</code> 인스턴스를 재시작할까요?",
4748
"restarted": "{name} 인스턴스를 재시작하였습니다.",
4849
"unregister": "<code>{name}</code> 인스턴스 등록을 해제할까요?",

0 commit comments

Comments
 (0)