refactor(gui-v2): cleanup(b) — AppTopbar wraps PrimeVue Menubar per RFC AD-3
This commit is contained in:
@@ -24,6 +24,10 @@
|
|||||||
*
|
*
|
||||||
* Icon convention: import Icon from '@/components/Icon.vue', <Icon name="tabler-x" :size="N" />
|
* Icon convention: import Icon from '@/components/Icon.vue', <Icon name="tabler-x" :size="N" />
|
||||||
*
|
*
|
||||||
|
* RFC AD-3: PrimeVue Menubar is the chrome root (wrap-don't-rewrite).
|
||||||
|
* Breadcrumb block → #start slot; search/notifications/user cluster → #end slot.
|
||||||
|
* :model="[]" — no Menubar menu items; Menubar is the AD-3 chrome shell only.
|
||||||
|
*
|
||||||
* Styling: crewli-starter CSS translated to Tailwind inline.
|
* Styling: crewli-starter CSS translated to Tailwind inline.
|
||||||
* <style scoped> used only for:
|
* <style scoped> used only for:
|
||||||
* 1. topbar-mark-shadow — inset box-shadow (no Tailwind utility at this granularity, RFC §7.4)
|
* 1. topbar-mark-shadow — inset box-shadow (no Tailwind utility at this granularity, RFC §7.4)
|
||||||
@@ -33,6 +37,7 @@
|
|||||||
import Avatar from 'primevue/avatar'
|
import Avatar from 'primevue/avatar'
|
||||||
import Breadcrumb from 'primevue/breadcrumb'
|
import Breadcrumb from 'primevue/breadcrumb'
|
||||||
import InputText from 'primevue/inputtext'
|
import InputText from 'primevue/inputtext'
|
||||||
|
import Menubar from 'primevue/menubar'
|
||||||
import Menu from 'primevue/menu'
|
import Menu from 'primevue/menu'
|
||||||
import OverlayBadge from 'primevue/overlaybadge'
|
import OverlayBadge from 'primevue/overlaybadge'
|
||||||
import Popover from 'primevue/popover'
|
import Popover from 'primevue/popover'
|
||||||
@@ -240,110 +245,121 @@ const userMenuItems = computed<MenuItem[]>(() => [
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!--
|
<!--
|
||||||
.topbar: sticky top-0, h-[var(--topbar-h)], flex items-center, gap-3,
|
RFC AD-3: PrimeVue Menubar is the chrome root.
|
||||||
bg-[var(--surface)], border-b border-[var(--border)], z-30, px-5
|
:model="[]" — no Menubar menu items; Menubar is the AD-3 chrome shell only.
|
||||||
|
:pt maps Menubar internal regions to the existing hand-rolled chrome classes
|
||||||
|
so visual layout is not gratuitously altered (pixel parity deferred to Bert).
|
||||||
-->
|
-->
|
||||||
<header class="sticky top-0 z-30 flex h-[var(--topbar-h,56px)] items-center gap-3 border-b border-[var(--p-content-border-color)] bg-[var(--p-content-background)] px-5">
|
<Menubar
|
||||||
<!-- Left group: hamburger + brand + breadcrumb -->
|
:model="[]"
|
||||||
<div class="flex items-center gap-[6px]">
|
:pt="{
|
||||||
<!--
|
root: 'sticky top-0 z-30 h-[var(--topbar-h,56px)] border-0 border-b border-[var(--p-content-border-color)] rounded-none bg-[var(--p-content-background)] px-5',
|
||||||
Hamburger — mobile only (lg:hidden).
|
start: 'flex items-center gap-[6px] min-w-0',
|
||||||
.hamburger: display none at >=lg, display inline-flex at <768px
|
end: 'ms-auto flex items-center gap-[6px]',
|
||||||
.icon-btn: 38x38, rounded, transparent bg, fg-muted color, hover bg-hover
|
}"
|
||||||
-->
|
>
|
||||||
<button
|
<template #start>
|
||||||
type="button"
|
<div data-tb="breadcrumb">
|
||||||
class="lg:hidden inline-flex h-[38px] w-[38px] items-center justify-center rounded-[var(--p-border-radius)] border-0 bg-transparent text-[var(--p-text-muted-color)] transition-colors duration-150 hover:bg-[var(--p-content-hover-background)] hover:text-[var(--p-text-color)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--p-primary-color)] focus-visible:outline-offset-1"
|
|
||||||
aria-label="Open menu"
|
|
||||||
@click="shell.setMobileOpen(true)"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name="tabler-menu-2"
|
|
||||||
:size="20"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Brand — hidden on mobile, shown >=lg.
|
|
||||||
.topbar-brand: display none default, display inline-flex >=lg
|
|
||||||
.mark: 28x28, rounded-lg, gradient bg, white text, font-bold 13px
|
|
||||||
.wordmark: font-bold 16px, tracking-tight
|
|
||||||
-->
|
|
||||||
<div class="hidden lg:inline-flex items-center gap-2">
|
|
||||||
<!--
|
<!--
|
||||||
Topbar mark — dynamic gradient cannot be a static Tailwind class.
|
Hamburger — mobile only (lg:hidden).
|
||||||
Inline style justified (RFC §7.4). Box-shadow via scoped CSS
|
.hamburger: display none at >=lg, display inline-flex at <768px
|
||||||
(inset-shadow has no Tailwind utility at this granularity).
|
.icon-btn: 38x38, rounded, transparent bg, fg-muted color, hover bg-hover
|
||||||
-->
|
-->
|
||||||
<div
|
<button
|
||||||
class="topbar-mark-shadow inline-flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-[13px] font-bold text-white"
|
type="button"
|
||||||
style="background: linear-gradient(135deg, var(--p-primary-500, #0d9488), var(--p-primary-700, #0f766e))"
|
class="lg:hidden inline-flex h-[38px] w-[38px] items-center justify-center rounded-[var(--p-border-radius)] border-0 bg-transparent text-[var(--p-text-muted-color)] transition-colors duration-150 hover:bg-[var(--p-content-hover-background)] hover:text-[var(--p-text-color)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--p-primary-color)] focus-visible:outline-offset-1"
|
||||||
aria-hidden="true"
|
aria-label="Open menu"
|
||||||
|
@click="shell.setMobileOpen(true)"
|
||||||
>
|
>
|
||||||
C
|
<Icon
|
||||||
|
name="tabler-menu-2"
|
||||||
|
:size="20"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Brand — hidden on mobile, shown >=lg.
|
||||||
|
.topbar-brand: display none default, display inline-flex >=lg
|
||||||
|
.mark: 28x28, rounded-lg, gradient bg, white text, font-bold 13px
|
||||||
|
.wordmark: font-bold 16px, tracking-tight
|
||||||
|
-->
|
||||||
|
<div class="hidden lg:inline-flex items-center gap-2">
|
||||||
|
<!--
|
||||||
|
Topbar mark — dynamic gradient cannot be a static Tailwind class.
|
||||||
|
Inline style justified (RFC §7.4). Box-shadow via scoped CSS
|
||||||
|
(inset-shadow has no Tailwind utility at this granularity).
|
||||||
|
-->
|
||||||
|
<div
|
||||||
|
class="topbar-mark-shadow inline-flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-[13px] font-bold text-white"
|
||||||
|
style="background: linear-gradient(135deg, var(--p-primary-500, #0d9488), var(--p-primary-700, #0f766e))"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
C
|
||||||
|
</div>
|
||||||
|
<span class="text-[16px] font-bold tracking-[-0.01em] text-[var(--p-text-color)]">
|
||||||
|
Crewli
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-[16px] font-bold tracking-[-0.01em] text-[var(--p-text-color)]">
|
|
||||||
Crewli
|
<!--
|
||||||
</span>
|
Breadcrumb — PrimeVue Breadcrumb, route-driven.
|
||||||
|
.breadcrumb: flex, items-center, gap-[6px], text-[13px], color fg-muted, flex-wrap nowrap, overflow-hidden
|
||||||
|
Hidden on mobile per crewli-starter (<768px: display:none!important)
|
||||||
|
-->
|
||||||
|
<nav
|
||||||
|
class="hidden lg:flex items-center"
|
||||||
|
aria-label="Breadcrumb"
|
||||||
|
>
|
||||||
|
<Breadcrumb
|
||||||
|
:model="breadcrumbModel"
|
||||||
|
class="border-0 bg-transparent p-0 text-[13px]"
|
||||||
|
/>
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!--
|
<template #end>
|
||||||
Breadcrumb — PrimeVue Breadcrumb, route-driven.
|
|
||||||
.breadcrumb: flex, items-center, gap-[6px], text-[13px], color fg-muted, flex-wrap nowrap, overflow-hidden
|
|
||||||
Hidden on mobile per crewli-starter (<768px: display:none!important)
|
|
||||||
-->
|
|
||||||
<nav
|
|
||||||
class="hidden lg:flex items-center"
|
|
||||||
aria-label="Breadcrumb"
|
|
||||||
>
|
|
||||||
<Breadcrumb
|
|
||||||
:model="breadcrumbModel"
|
|
||||||
class="border-0 bg-transparent p-0 text-[13px]"
|
|
||||||
/>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Right group: ws-mobile-btn, search, density, theme, notifications, user -->
|
|
||||||
<div class="ms-auto flex items-center gap-[6px]">
|
|
||||||
<!--
|
<!--
|
||||||
Mobile workspace button — visible only <lg.
|
Mobile workspace button — visible only <lg.
|
||||||
.ws-mobile-btn: display none (>= 1024px). On mobile: display flex.
|
.ws-mobile-btn: display none (>= 1024px). On mobile: display flex.
|
||||||
38x38, rounded, color #fff, font-bold 12px, gradient bg via inline :style
|
38x38, rounded, color #fff, font-bold 12px, gradient bg via inline :style
|
||||||
(dynamic hex pair — RFC §7.4). Box-shadow via scoped CSS.
|
(dynamic hex pair — RFC §7.4). Box-shadow via scoped CSS.
|
||||||
-->
|
-->
|
||||||
<button
|
<div data-tb="search">
|
||||||
v-if="authStore.currentOrganisation"
|
<button
|
||||||
type="button"
|
v-if="authStore.currentOrganisation"
|
||||||
class="ws-mobile-btn-shadow lg:hidden inline-flex h-[38px] w-[38px] flex-shrink-0 items-center justify-center rounded-[var(--p-border-radius)] border-0 text-[12px] font-bold text-white"
|
type="button"
|
||||||
:style="{
|
class="ws-mobile-btn-shadow lg:hidden inline-flex h-[38px] w-[38px] flex-shrink-0 items-center justify-center rounded-[var(--p-border-radius)] border-0 text-[12px] font-bold text-white"
|
||||||
background: `linear-gradient(135deg, ${mobileOrgGradient[0]}, ${mobileOrgGradient[1]})`,
|
:style="{
|
||||||
}"
|
background: `linear-gradient(135deg, ${mobileOrgGradient[0]}, ${mobileOrgGradient[1]})`,
|
||||||
:aria-label="`Workspace: ${authStore.currentOrganisation.name}`"
|
}"
|
||||||
>
|
:aria-label="`Workspace: ${authStore.currentOrganisation.name}`"
|
||||||
{{ mobileOrgInitials }}
|
>
|
||||||
</button>
|
{{ mobileOrgInitials }}
|
||||||
|
</button>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Search — static chrome, no backend.
|
Search — static chrome, no backend.
|
||||||
.search: position relative, w-[320px], max-w-full
|
.search: position relative, w-[320px], max-w-full
|
||||||
.search input: h-[38px], px-[12px] ps-[36px], bg-surface-alt, border rounded
|
.search input: h-[38px], px-[12px] ps-[36px], bg-surface-alt, border rounded
|
||||||
.search .kbd: absolute right-2, text-[11px], px-[6px] py-[2px], border rounded
|
.search .kbd: absolute right-2, text-[11px], px-[6px] py-[2px], border rounded
|
||||||
Hidden on smallest viewports per crewli-starter (<768px: width 0 / display:none)
|
Hidden on smallest viewports per crewli-starter (<768px: width 0 / display:none)
|
||||||
-->
|
-->
|
||||||
<div class="relative hidden sm:block w-[240px] lg:w-[320px] max-w-full">
|
<div class="relative hidden sm:block w-[240px] lg:w-[320px] max-w-full">
|
||||||
<Icon
|
<Icon
|
||||||
name="tabler-search"
|
name="tabler-search"
|
||||||
:size="16"
|
:size="16"
|
||||||
class="pointer-events-none absolute left-[11px] top-1/2 -translate-y-1/2 text-[var(--p-text-muted-color)]"
|
class="pointer-events-none absolute left-[11px] top-1/2 -translate-y-1/2 text-[var(--p-text-muted-color)]"
|
||||||
/>
|
/>
|
||||||
<InputText
|
<InputText
|
||||||
class="h-[38px] w-full rounded-[var(--p-border-radius)] border border-transparent bg-[var(--p-content-hover-background)] ps-[36px] pe-[52px] text-[var(--p-text-color)] placeholder:text-[var(--p-text-muted-color)] focus:border-[var(--p-primary-color)] focus:bg-[var(--p-content-background)] focus:shadow-[0_0_0_3px_var(--p-primary-50)] focus:outline-none transition-[border-color,background,box-shadow] duration-150"
|
class="h-[38px] w-full rounded-[var(--p-border-radius)] border border-transparent bg-[var(--p-content-hover-background)] ps-[36px] pe-[52px] text-[var(--p-text-color)] placeholder:text-[var(--p-text-muted-color)] focus:border-[var(--p-primary-color)] focus:bg-[var(--p-content-background)] focus:shadow-[0_0_0_3px_var(--p-primary-50)] focus:outline-none transition-[border-color,background,box-shadow] duration-150"
|
||||||
placeholder="Search artists, crew, events..."
|
placeholder="Search artists, crew, events..."
|
||||||
:pt="{ root: { 'data-search-input': '' } }"
|
:pt="{ root: { 'data-search-input': '' } }"
|
||||||
/>
|
/>
|
||||||
<span class="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 rounded-[var(--p-border-radius-sm,4px)] border border-[var(--p-content-border-color)] bg-[var(--p-content-background)] px-[6px] py-[2px] font-mono text-[11px] text-[var(--p-text-muted-color)]">
|
<span class="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 rounded-[var(--p-border-radius-sm,4px)] border border-[var(--p-content-border-color)] bg-[var(--p-content-background)] px-[6px] py-[2px] font-mono text-[11px] text-[var(--p-text-muted-color)]">
|
||||||
⌘K
|
⌘K
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@@ -380,28 +396,30 @@ const userMenuItems = computed<MenuItem[]>(() => [
|
|||||||
PrimeVue Popover replaces crewli-starter's manual document.addEventListener('mousedown', …)
|
PrimeVue Popover replaces crewli-starter's manual document.addEventListener('mousedown', …)
|
||||||
TODO TECH-WS-GUI-REDESIGN: real notification feed — not foundation scope
|
TODO TECH-WS-GUI-REDESIGN: real notification feed — not foundation scope
|
||||||
-->
|
-->
|
||||||
<OverlayBadge
|
<div data-tb="notifications">
|
||||||
:value="notifCount > 0 ? String(notifCount) : undefined"
|
<OverlayBadge
|
||||||
severity="danger"
|
:value="notifCount > 0 ? String(notifCount) : undefined"
|
||||||
>
|
severity="danger"
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="inline-flex h-[38px] w-[38px] items-center justify-center rounded-[var(--p-border-radius)] border-0 bg-transparent text-[var(--p-text-muted-color)] transition-colors duration-150 hover:bg-[var(--p-content-hover-background)] hover:text-[var(--p-text-color)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--p-primary-color)] focus-visible:outline-offset-1"
|
|
||||||
aria-label="Notifications"
|
|
||||||
@click="toggleNotifPopover"
|
|
||||||
>
|
>
|
||||||
<Icon
|
<button
|
||||||
name="tabler-bell"
|
type="button"
|
||||||
:size="18"
|
class="inline-flex h-[38px] w-[38px] items-center justify-center rounded-[var(--p-border-radius)] border-0 bg-transparent text-[var(--p-text-muted-color)] transition-colors duration-150 hover:bg-[var(--p-content-hover-background)] hover:text-[var(--p-text-color)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--p-primary-color)] focus-visible:outline-offset-1"
|
||||||
/>
|
aria-label="Notifications"
|
||||||
</button>
|
@click="toggleNotifPopover"
|
||||||
</OverlayBadge>
|
>
|
||||||
|
<Icon
|
||||||
|
name="tabler-bell"
|
||||||
|
:size="18"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</OverlayBadge>
|
||||||
|
|
||||||
<Popover ref="notifPopoverRef">
|
<Popover ref="notifPopoverRef">
|
||||||
<div class="min-w-[320px] p-4 text-[13px] text-[var(--p-text-muted-color)]">
|
<div class="min-w-[320px] p-4 text-[13px] text-[var(--p-text-muted-color)]">
|
||||||
No notifications
|
No notifications
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
User menu — PrimeVue Avatar + Menu.
|
User menu — PrimeVue Avatar + Menu.
|
||||||
@@ -409,40 +427,42 @@ const userMenuItems = computed<MenuItem[]>(() => [
|
|||||||
Menu: PrimeVue Menu replaces crewli-starter's manual click-outside listener.
|
Menu: PrimeVue Menu replaces crewli-starter's manual click-outside listener.
|
||||||
#start slot used for user info header.
|
#start slot used for user info header.
|
||||||
-->
|
-->
|
||||||
<Avatar
|
<div data-tb="user">
|
||||||
:label="userInitials"
|
<Avatar
|
||||||
shape="circle"
|
:label="userInitials"
|
||||||
class="cursor-pointer"
|
shape="circle"
|
||||||
:pt="{ root: { style: 'background: linear-gradient(135deg, #f472b6, var(--p-primary-500, #0d9488)); color: #fff;' } }"
|
class="cursor-pointer"
|
||||||
aria-label="User menu"
|
:pt="{ root: { style: 'background: linear-gradient(135deg, #f472b6, var(--p-primary-500, #0d9488)); color: #fff;' } }"
|
||||||
@click="toggleUserMenu"
|
aria-label="User menu"
|
||||||
/>
|
@click="toggleUserMenu"
|
||||||
|
/>
|
||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
ref="userMenuRef"
|
ref="userMenuRef"
|
||||||
:model="userMenuItems"
|
:model="userMenuItems"
|
||||||
popup
|
popup
|
||||||
>
|
>
|
||||||
<template #start>
|
<template #start>
|
||||||
<div class="flex items-center gap-3 px-4 py-3 border-b border-[var(--p-content-border-color)]">
|
<div class="flex items-center gap-3 px-4 py-3 border-b border-[var(--p-content-border-color)]">
|
||||||
<Avatar
|
<Avatar
|
||||||
:label="userInitials"
|
:label="userInitials"
|
||||||
shape="circle"
|
shape="circle"
|
||||||
:pt="{ root: { style: 'background: linear-gradient(135deg, #f472b6, var(--p-primary-500, #0d9488)); color: #fff; width:40px; height:40px; font-size:14px;' } }"
|
:pt="{ root: { style: 'background: linear-gradient(135deg, #f472b6, var(--p-primary-500, #0d9488)); color: #fff; width:40px; height:40px; font-size:14px;' } }"
|
||||||
/>
|
/>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<div class="truncate text-[13.5px] font-semibold text-[var(--p-text-color)]">
|
<div class="truncate text-[13.5px] font-semibold text-[var(--p-text-color)]">
|
||||||
{{ authStore.user?.full_name ?? '' }}
|
{{ authStore.user?.full_name ?? '' }}
|
||||||
</div>
|
</div>
|
||||||
<div class="truncate text-[12px] text-[var(--p-text-muted-color)]">
|
<div class="truncate text-[12px] text-[var(--p-text-muted-color)]">
|
||||||
{{ authStore.user?.email ?? '' }}
|
{{ authStore.user?.email ?? '' }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</Menu>
|
||||||
</Menu>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</header>
|
</Menubar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -50,7 +50,19 @@ vi.mock('vue-router', () => ({
|
|||||||
// Stubs for PrimeVue components
|
// Stubs for PrimeVue components
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MenubarStub — mirrors the PrimeVue Menubar slot contract used by AppTopbar
|
||||||
|
* (RFC AD-3). Renders #start and #end named slots so [data-tb="breadcrumb"]
|
||||||
|
* and [data-tb="user"] are inspectable inside the stub.
|
||||||
|
*/
|
||||||
|
const MenubarStub = {
|
||||||
|
name: 'Menubar',
|
||||||
|
props: ['model', 'pt'],
|
||||||
|
template: '<div class="menubar-stub"><slot name="start" /><slot name="end" /></div>',
|
||||||
|
}
|
||||||
|
|
||||||
const globalStubs = {
|
const globalStubs = {
|
||||||
|
Menubar: MenubarStub,
|
||||||
Breadcrumb: { name: 'Breadcrumb', props: ['model'], template: '<nav class="breadcrumb-stub" />' },
|
Breadcrumb: { name: 'Breadcrumb', props: ['model'], template: '<nav class="breadcrumb-stub" />' },
|
||||||
InputText: { name: 'InputText', template: '<input class="input-text-stub" />' },
|
InputText: { name: 'InputText', template: '<input class="input-text-stub" />' },
|
||||||
OverlayBadge: { name: 'OverlayBadge', props: ['value', 'severity'], template: '<div class="overlay-badge-stub"><slot /></div>' },
|
OverlayBadge: { name: 'OverlayBadge', props: ['value', 'severity'], template: '<div class="overlay-badge-stub"><slot /></div>' },
|
||||||
@@ -286,4 +298,22 @@ describe('AppTopbar', () => {
|
|||||||
expect(lastItem.command).toBeUndefined()
|
expect(lastItem.command).toBeUndefined()
|
||||||
expect('route' in lastItem).toBe(false)
|
expect('route' in lastItem).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// RFC AD-3: AppTopbar wraps PrimeVue Menubar as chrome root
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
it('renders the top bar inside a PrimeVue Menubar (RFC AD-3)', () => {
|
||||||
|
const w = mountTopbar()
|
||||||
|
|
||||||
|
expect(w.findComponent(MenubarStub).exists()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('breadcrumb lives in Menubar #start, user cluster in #end', () => {
|
||||||
|
const w = mountTopbar()
|
||||||
|
const bar = w.findComponent(MenubarStub)
|
||||||
|
|
||||||
|
expect(bar.find('[data-tb="breadcrumb"]').exists()).toBe(true)
|
||||||
|
expect(bar.find('[data-tb="user"]').exists()).toBe(true)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user