Generate JHipster 9.3.0 baseline application

This commit is contained in:
jieun committed 2026-10-20 17:50:01 +09:00
1 parent 0a9b7b923f
commit 1d97cc4e31
345 files changed
+25563

No files matched your search

@@ -0,0 +1,110 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { computed } from 'vue';
import { type Router } from 'vue-router';
import { createTestingPinia } from '@pinia/testing';
import { shallowMount } from '@vue/test-utils';
import { useLoginModal } from '@/account/login-modal';
import { createRouter } from '@/router';
import { useStore } from '@/store';
import JhiNavbar from './jhi-navbar.vue';
type JhiNavbarComponentType = InstanceType<typeof JhiNavbar>;
const pinia = createTestingPinia({ stubActions: false });
const store = useStore();
describe('JhiNavbar', () => {
let jhiNavbar: JhiNavbarComponentType;
let login: ReturnType<typeof useLoginModal>;
const accountService = { hasAnyAuthorityAndCheckAuth: vi.fn().mockImplementation(() => Promise.resolve(true)) };
const changeLanguage = vi.fn();
let router: Router;
beforeEach(() => {
router = createRouter();
const wrapper = shallowMount(JhiNavbar, {
global: {
plugins: [pinia, router],
stubs: {
'font-awesome-icon': true,
'b-navbar': true,
'b-navbar-nav': true,
'b-dropdown-item': true,
'b-collapse': true,
'b-nav-item': true,
'b-nav-item-dropdown': true,
'b-navbar-toggle': true,
'b-navbar-brand': true,
},
provide: {
currentLanguage: computed(() => 'foo'),
changeLanguage,
accountService,
},
},
});
jhiNavbar = wrapper.vm;
login = useLoginModal();
});
it('should not have user data set', () => {
expect(jhiNavbar.authenticated).toBeFalsy();
expect(jhiNavbar.openAPIEnabled).toBeFalsy();
expect(jhiNavbar.inProduction).toBeFalsy();
});
it('should have user data set after authentication', () => {
store.setAuthentication({ login: 'test' });
expect(jhiNavbar.authenticated).toBeTruthy();
});
it('should have profile info set after info retrieved', () => {
store.setActiveProfiles(['prod', 'api-docs']);
expect(jhiNavbar.openAPIEnabled).toBeTruthy();
expect(jhiNavbar.inProduction).toBeTruthy();
});
it('should use login service', () => {
jhiNavbar.showLogin();
expect(login.showLogin).toHaveBeenCalled();
});
it('should use account service', () => {
jhiNavbar.hasAnyAuthority('auth');
expect(accountService.hasAnyAuthorityAndCheckAuth).toHaveBeenCalled();
});
it('logout should clear credentials and return to the home page when no provider URL is returned', async () => {
store.setAuthentication({ login: 'test' });
await jhiNavbar.logout();
expect(jhiNavbar.authenticated).toBeFalsy();
});
it('should determine active route', async () => {
await router.push('/forbidden');
expect(jhiNavbar.subIsActive('/titi')).toBeFalsy();
expect(jhiNavbar.subIsActive('/forbidden')).toBeTruthy();
expect(jhiNavbar.subIsActive(['/forbidden', 'forbidden'])).toBeTruthy();
});
it('should call translationService when changing language', () => {
jhiNavbar.changeLanguage('fr');
expect(changeLanguage).toHaveBeenCalled();
});
it('should check for correct language', () => {
expect(jhiNavbar.isActiveLanguage('en')).toBeFalsy();
expect(jhiNavbar.isActiveLanguage('foo')).toBeTruthy();
});
});
@@ -0,0 +1,79 @@
import { type Ref, computed, defineComponent, inject, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { storeToRefs } from 'pinia';
import type AccountService from '@/account/account.service';
import { useLoginModal } from '@/account/login-modal';
import EntitiesMenu from '@/entities/entities-menu.vue';
import languages from '@/shared/config/languages';
import { AUTHENTICATION_TOKEN_KEY } from '@/shared/jhipster/constants';
import { useStore } from '@/store';
export default defineComponent({
name: 'JhiNavbar',
components: {
'entities-menu': EntitiesMenu,
},
setup() {
const { showLogin } = useLoginModal();
const accountService = inject<AccountService>('accountService');
const currentLanguage = inject('currentLanguage', () => computed(() => navigator.language ?? 'en'), true);
const changeLanguage = inject<(string) => Promise<void>>('changeLanguage');
const isActiveLanguage = (key: string) => key === currentLanguage.value;
const router = useRouter();
const store = useStore();
const version = `v${APP_VERSION}`;
const hasAnyAuthorityValues: Ref = ref({});
const openAPIEnabled = computed(() => store.activeProfiles.includes('api-docs'));
const inProduction = computed(() => store.activeProfiles.includes('prod'));
const { authenticated } = storeToRefs(store);
const subIsActive = (input: string | string[]) => {
const paths = Array.isArray(input) ? input : [input];
// current path starts with this path string
return paths.some(path => router.currentRoute.value.path.startsWith(path));
};
const logout = async () => {
localStorage.removeItem(AUTHENTICATION_TOKEN_KEY);
sessionStorage.removeItem(AUTHENTICATION_TOKEN_KEY);
store.logout();
if (router.currentRoute.value.path !== '/') {
await router.push('/');
}
};
return {
logout,
subIsActive,
accountService,
showLogin,
changeLanguage,
languages: languages(),
isActiveLanguage,
version,
currentLanguage,
hasAnyAuthorityValues,
openAPIEnabled,
inProduction,
authenticated,
t$: useI18n().t,
};
},
methods: {
hasAnyAuthority(authorities: any): boolean {
this.accountService.hasAnyAuthorityAndCheckAuth(authorities).then(value => {
if (this.hasAnyAuthorityValues[authorities] !== value) {
this.hasAnyAuthorityValues = { ...this.hasAnyAuthorityValues, [authorities]: value };
}
});
return this.hasAnyAuthorityValues[authorities] ?? false;
},
},
});
@@ -0,0 +1,193 @@
<template>
<b-navbar data-cy="navbar" toggleable="md" variant="dark" data-bs-theme="dark">
<b-navbar-brand class="logo" to="/">
<span class="logo-img"></span>
<span class="navbar-title">{{ t$('global.title') }}</span> <span class="navbar-version">{{ version }}</span>
</b-navbar-brand>
<b-navbar-toggle
right
class="jh-navbar-toggler d-lg-none"
data-toggle="collapse"
target="header-tabs"
aria-expanded="false"
aria-label="Toggle navigation"
>
<font-awesome-icon icon="bars" />
</b-navbar-toggle>
<b-collapse is-nav id="header-tabs">
<b-navbar-nav class="ms-auto">
<b-nav-item to="/" exact>
<span>
<font-awesome-icon icon="fa-solid fa-home" />
<span>{{ t$('global.menu.home') }}</span>
</span>
</b-nav-item>
<b-nav-item-dropdown
:no-size="true"
end
id="entity-menu"
v-if="authenticated"
active-class="active"
class="pointer"
data-cy="entity"
>
<template #button-content>
<span class="navbar-dropdown-menu">
<font-awesome-icon icon="th-list" />
<span class="no-bold">{{ t$('global.menu.entities.main') }}</span>
</span>
</template>
<entities-menu></entities-menu>
<!-- jhipster-needle-add-entity-to-menu - JHipster will add entities to the menu here -->
</b-nav-item-dropdown>
<b-nav-item-dropdown
right
id="admin-menu"
v-if="hasAnyAuthority('ROLE_ADMIN') && authenticated"
:class="{ 'router-link-active': subIsActive('/admin') }"
active-class="active"
class="pointer"
data-cy="adminMenu"
>
<template #button-content>
<span class="navbar-dropdown-menu">
<font-awesome-icon icon="users-cog" />
<span class="no-bold">{{ t$('global.menu.admin.main') }}</span>
</span>
</template>
<b-dropdown-item to="/admin/user-management" active-class="active">
<font-awesome-icon icon="users" />
<span>{{ t$('global.menu.admin.userManagement') }}</span>
</b-dropdown-item>
<b-dropdown-item to="/admin/metrics" active-class="active">
<font-awesome-icon icon="tachometer-alt" />
<span>{{ t$('global.menu.admin.metrics') }}</span>
</b-dropdown-item>
<b-dropdown-item to="/admin/health" active-class="active">
<font-awesome-icon icon="heart" />
<span>{{ t$('global.menu.admin.health') }}</span>
</b-dropdown-item>
<b-dropdown-item to="/admin/configuration" active-class="active">
<font-awesome-icon icon="cogs" />
<span>{{ t$('global.menu.admin.configuration') }}</span>
</b-dropdown-item>
<b-dropdown-item to="/admin/logs" active-class="active">
<font-awesome-icon icon="tasks" />
<span>{{ t$('global.menu.admin.logs') }}</span>
</b-dropdown-item>
<b-dropdown-item v-if="openAPIEnabled" to="/admin/docs" active-class="active">
<font-awesome-icon icon="book" />
<span>{{ t$('global.menu.admin.apidocs') }}</span>
</b-dropdown-item>
</b-nav-item-dropdown>
<b-nav-item-dropdown id="languagesnavBarDropdown" end v-if="languages && Object.keys(languages).length > 1">
<template #button-content>
<font-awesome-icon icon="flag" />
<span class="no-bold">{{ t$('global.menu.language') }}</span>
</template>
<b-dropdown-item
v-for="(value, key) in languages"
:key="`lang-${key}`"
@click="changeLanguage(key)"
:class="{ active: isActiveLanguage(key) }"
>
{{ value.name }}
</b-dropdown-item>
</b-nav-item-dropdown>
<b-nav-item-dropdown
right
id="account-menu"
:class="{ 'router-link-active': subIsActive('/account') }"
active-class="active"
class="pointer"
data-cy="accountMenu"
>
<template #button-content>
<span class="navbar-dropdown-menu">
<font-awesome-icon icon="user" />
<span class="no-bold">{{ t$('global.menu.account.main') }}</span>
</span>
</template>
<b-dropdown-item data-cy="settings" to="/account/settings" v-if="authenticated" active-class="active">
<font-awesome-icon icon="wrench" />
<span>{{ t$('global.menu.account.settings') }}</span>
</b-dropdown-item>
<b-dropdown-item data-cy="passwordItem" to="/account/password" v-if="authenticated" active-class="active">
<font-awesome-icon icon="lock" />
<span>{{ t$('global.menu.account.password') }}</span>
</b-dropdown-item>
<b-dropdown-item data-cy="logout" v-if="authenticated" @click="logout()" id="logout" active-class="active">
<font-awesome-icon icon="sign-out-alt" />
<span>{{ t$('global.menu.account.logout') }}</span>
</b-dropdown-item>
<b-dropdown-item data-cy="login" v-if="!authenticated" @click="showLogin()" id="login" active-class="active">
<font-awesome-icon icon="sign-in-alt" />
<span>{{ t$('global.menu.account.login') }}</span>
</b-dropdown-item>
<b-dropdown-item data-cy="register" to="/register" id="register" v-if="!authenticated" active-class="active">
<font-awesome-icon icon="user-plus" />
<span>{{ t$('global.menu.account.register') }}</span>
</b-dropdown-item>
</b-nav-item-dropdown>
</b-navbar-nav>
</b-collapse>
</b-navbar>
</template>
<script lang="ts" src="./jhi-navbar.component.ts"></script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
/* ==========================================================================
Navbar
========================================================================== */
.navbar-version {
font-size: 0.65em;
color: #ccc;
}
.navbar .navbar-nav .nav-item {
margin-right: 0.5rem;
}
@media screen and (min-width: 768px) {
.jh-navbar-toggler {
display: none;
}
}
@media screen and (min-width: 768px) and (max-width: 1150px) {
span span {
display: none;
}
}
.navbar-title {
display: inline-block;
color: white;
}
/* ==========================================================================
Logo styles
========================================================================== */
.navbar-brand.logo {
padding: 0 7px;
}
.logo .logo-img {
height: 45px;
display: inline-block;
vertical-align: middle;
width: 45px;
}
.logo-img {
height: 100%;
background: url('/content/images/logo-jhipster.png') no-repeat center center;
background-size: contain;
width: 100%;
filter: drop-shadow(0 0 0.05rem white);
margin: 0 5px;
}
</style>