fix: refine slash command menu
This commit is contained in:
@@ -122,16 +122,16 @@
|
||||
matches our vault theme instead of BlockNote's hardcoded light/dark defaults. */
|
||||
--bn-colors-editor-background: var(--bg-primary);
|
||||
--bn-colors-editor-text: var(--text-primary);
|
||||
--bn-colors-menu-background: var(--bg-card);
|
||||
--bn-colors-menu-background: var(--surface-popover);
|
||||
--bn-colors-menu-text: var(--text-primary);
|
||||
--bn-colors-tooltip-background: var(--bg-hover);
|
||||
--bn-colors-tooltip-background: var(--state-hover-subtle);
|
||||
--bn-colors-tooltip-text: var(--text-primary);
|
||||
--bn-colors-hovered-background: var(--bg-hover);
|
||||
--bn-colors-hovered-background: var(--state-hover);
|
||||
--bn-colors-hovered-text: var(--text-primary);
|
||||
--bn-colors-selected-background: var(--bg-selected);
|
||||
--bn-colors-selected-text: var(--text-primary);
|
||||
--bn-colors-border: var(--border-primary);
|
||||
--bn-colors-shadow: var(--border-primary);
|
||||
--bn-colors-border: var(--border-dialog);
|
||||
--bn-colors-shadow: var(--shadow-dialog);
|
||||
--bn-colors-side-menu: var(--text-muted);
|
||||
}
|
||||
|
||||
@@ -140,6 +140,84 @@
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .bn-suggestion-menu {
|
||||
border: 1px solid var(--border-dialog) !important;
|
||||
background: var(--surface-popover) !important;
|
||||
box-shadow: 0 8px 32px var(--shadow-dialog) !important;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .bn-suggestion-menu-label {
|
||||
color: var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .bn-suggestion-menu-item {
|
||||
color: var(--text-primary) !important;
|
||||
height: 34px !important;
|
||||
min-height: 34px !important;
|
||||
padding: 3px 8px !important;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .bn-mt-suggestion-menu-item-section[data-position="left"] {
|
||||
width: 28px;
|
||||
min-width: 28px;
|
||||
margin-inline-end: 8px !important;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
border-radius: 0 !important;
|
||||
color: var(--text-primary) !important;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .bn-mt-suggestion-menu-item-section[data-position="right"] {
|
||||
margin-inline-start: 12px !important;
|
||||
color: var(--text-muted) !important;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .bn-mt-suggestion-menu-item-section[data-position="right"] .mantine-Badge-root {
|
||||
min-width: 0 !important;
|
||||
height: auto !important;
|
||||
padding: 0 !important;
|
||||
border: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
background: transparent !important;
|
||||
color: var(--text-muted) !important;
|
||||
font-size: 10px !important;
|
||||
font-weight: 500 !important;
|
||||
line-height: 1.2 !important;
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .bn-mt-suggestion-menu-item-body {
|
||||
justify-content: center !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .bn-mt-suggestion-menu-item-subtitle {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .tolaria-slash-menu-icon {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .tolaria-slash-menu-icon__fill {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .bn-suggestion-menu-item:is(:hover, [aria-selected="true"]) .tolaria-slash-menu-icon__regular {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.editor__blocknote-container .bn-suggestion-menu-item:is(:hover, [aria-selected="true"]) .tolaria-slash-menu-icon__fill {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* =============================================
|
||||
Bear-style live preview: zero horizontal shift
|
||||
============================================= */
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Children, isValidElement, type ReactElement } from 'react'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getFormattingToolbarItems } from '@blocknote/react'
|
||||
import {
|
||||
@@ -44,20 +45,24 @@ describe('tolariaEditorFormatting', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('filters unsupported toggle slash-menu variants and annotates supported markdown commands', () => {
|
||||
it('filters unsupported toggle slash-menu variants and removes command descriptions', () => {
|
||||
type TolariaSlashMenuTestItem = {
|
||||
key: string
|
||||
title: string
|
||||
onItemClick: () => void
|
||||
subtext?: string
|
||||
icon?: ReactElement
|
||||
}
|
||||
|
||||
const items = filterTolariaSlashMenuItems([
|
||||
{ key: 'toggle_heading', title: 'Toggle heading', onItemClick: () => {} },
|
||||
{ key: 'toggle_list', title: 'Toggle list', onItemClick: () => {} },
|
||||
{ key: 'heading', title: 'Heading', onItemClick: () => {} },
|
||||
{ key: 'bullet_list', title: 'Bullet List', onItemClick: () => {} },
|
||||
{ key: 'code_block', title: 'Code Block', onItemClick: () => {} },
|
||||
{ key: 'heading', title: 'Heading', subtext: 'Default heading copy', onItemClick: () => {} },
|
||||
{ key: 'bullet_list', title: 'Bullet List', subtext: 'Default list copy', onItemClick: () => {} },
|
||||
{ key: 'code_block', title: 'Code Block', subtext: 'Default code copy', onItemClick: () => {} },
|
||||
{ key: 'heading_4', title: 'Heading 4', onItemClick: () => {} },
|
||||
{ key: 'heading_5', title: 'Heading 5', onItemClick: () => {} },
|
||||
{ key: 'heading_6', title: 'Heading 6', onItemClick: () => {} },
|
||||
] satisfies TolariaSlashMenuTestItem[])
|
||||
|
||||
expect(items.map((item) => item.key)).toEqual([
|
||||
@@ -65,14 +70,40 @@ describe('tolariaEditorFormatting', () => {
|
||||
'bullet_list',
|
||||
'code_block',
|
||||
])
|
||||
expect(items.find((item) => item.key === 'heading')?.subtext).toContain(
|
||||
'Markdown-safe heading',
|
||||
)
|
||||
expect(items.find((item) => item.key === 'bullet_list')?.subtext).toContain(
|
||||
'Markdown-safe bullet list',
|
||||
)
|
||||
expect(items.find((item) => item.key === 'code_block')?.subtext).toContain(
|
||||
'Markdown-safe fenced code block',
|
||||
)
|
||||
expect(items.map((item) => item.subtext)).toEqual([
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
])
|
||||
})
|
||||
|
||||
it('wraps slash-menu icons so hover can swap Phosphor weights', () => {
|
||||
type TolariaSlashMenuTestItem = {
|
||||
key: string
|
||||
title: string
|
||||
onItemClick: () => void
|
||||
icon?: ReactElement
|
||||
}
|
||||
|
||||
const items = filterTolariaSlashMenuItems([
|
||||
{ key: 'heading', title: 'Heading', onItemClick: () => {} },
|
||||
] satisfies TolariaSlashMenuTestItem[])
|
||||
const icon = items[0]?.icon
|
||||
|
||||
expect(isValidElement(icon)).toBe(true)
|
||||
if (!isValidElement<{ className?: string; children?: ReactElement[] }>(icon)) return
|
||||
|
||||
const iconChildren = Children.toArray(icon.props.children) as Array<
|
||||
ReactElement<{ className?: string; weight?: string }>
|
||||
>
|
||||
expect(icon.props.className).toBe('tolaria-slash-menu-icon')
|
||||
expect(iconChildren.map((child) => child.props.className)).toEqual([
|
||||
'tolaria-slash-menu-icon__regular',
|
||||
'tolaria-slash-menu-icon__fill',
|
||||
])
|
||||
expect(iconChildren.map((child) => child.props.weight)).toEqual([
|
||||
'regular',
|
||||
'fill',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,29 +3,36 @@ import {
|
||||
getDefaultReactSlashMenuItems,
|
||||
type DefaultReactSuggestionItem,
|
||||
} from '@blocknote/react'
|
||||
import type { ReactElement } from 'react'
|
||||
import { createElement, type ReactElement } from 'react'
|
||||
import {
|
||||
Code2,
|
||||
Heading1,
|
||||
Heading2,
|
||||
Heading3,
|
||||
Heading4,
|
||||
Heading5,
|
||||
Heading6,
|
||||
List,
|
||||
CodeBlock,
|
||||
File,
|
||||
ImageSquare,
|
||||
ListBullets,
|
||||
ListChecks,
|
||||
ListOrdered,
|
||||
Pilcrow,
|
||||
Quote,
|
||||
type LucideIcon,
|
||||
} from 'lucide-react'
|
||||
ListNumbers,
|
||||
Minus,
|
||||
Paragraph,
|
||||
Quotes,
|
||||
Smiley,
|
||||
SpeakerHigh,
|
||||
Table,
|
||||
TextHOne,
|
||||
TextHTwo,
|
||||
TextHThree,
|
||||
TextHFour,
|
||||
TextHFive,
|
||||
TextHSix,
|
||||
Video,
|
||||
type Icon as PhosphorIcon,
|
||||
} from '@phosphor-icons/react'
|
||||
|
||||
type TolariaSlashMenuItem = DefaultReactSuggestionItem & { key: string }
|
||||
type TolariaBlockTypeSelectItem = {
|
||||
name: string
|
||||
type: string
|
||||
props?: Record<string, boolean | number | string>
|
||||
icon: LucideIcon
|
||||
icon: PhosphorIcon
|
||||
}
|
||||
|
||||
const UNSUPPORTED_FORMATTING_TOOLBAR_KEYS = new Set([
|
||||
@@ -37,6 +44,9 @@ const UNSUPPORTED_FORMATTING_TOOLBAR_KEYS = new Set([
|
||||
])
|
||||
|
||||
const UNSUPPORTED_SLASH_MENU_KEYS = new Set([
|
||||
'heading_4',
|
||||
'heading_5',
|
||||
'heading_6',
|
||||
'toggle_heading',
|
||||
'toggle_heading_2',
|
||||
'toggle_heading_3',
|
||||
@@ -44,33 +54,60 @@ const UNSUPPORTED_SLASH_MENU_KEYS = new Set([
|
||||
])
|
||||
|
||||
const TOLARIA_BLOCK_TYPE_SELECT_ITEMS: TolariaBlockTypeSelectItem[] = [
|
||||
{ name: 'Paragraph', type: 'paragraph', icon: Pilcrow },
|
||||
{ name: 'Heading 1', type: 'heading', props: { level: 1 }, icon: Heading1 },
|
||||
{ name: 'Heading 2', type: 'heading', props: { level: 2 }, icon: Heading2 },
|
||||
{ name: 'Heading 3', type: 'heading', props: { level: 3 }, icon: Heading3 },
|
||||
{ name: 'Heading 4', type: 'heading', props: { level: 4 }, icon: Heading4 },
|
||||
{ name: 'Heading 5', type: 'heading', props: { level: 5 }, icon: Heading5 },
|
||||
{ name: 'Heading 6', type: 'heading', props: { level: 6 }, icon: Heading6 },
|
||||
{ name: 'Quote', type: 'quote', icon: Quote },
|
||||
{ name: 'Bullet List', type: 'bulletListItem', icon: List },
|
||||
{ name: 'Numbered List', type: 'numberedListItem', icon: ListOrdered },
|
||||
{ name: 'Paragraph', type: 'paragraph', icon: Paragraph },
|
||||
{ name: 'Heading 1', type: 'heading', props: { level: 1 }, icon: TextHOne },
|
||||
{ name: 'Heading 2', type: 'heading', props: { level: 2 }, icon: TextHTwo },
|
||||
{ name: 'Heading 3', type: 'heading', props: { level: 3 }, icon: TextHThree },
|
||||
{ name: 'Heading 4', type: 'heading', props: { level: 4 }, icon: TextHFour },
|
||||
{ name: 'Heading 5', type: 'heading', props: { level: 5 }, icon: TextHFive },
|
||||
{ name: 'Heading 6', type: 'heading', props: { level: 6 }, icon: TextHSix },
|
||||
{ name: 'Quote', type: 'quote', icon: Quotes },
|
||||
{ name: 'Bullet List', type: 'bulletListItem', icon: ListBullets },
|
||||
{ name: 'Numbered List', type: 'numberedListItem', icon: ListNumbers },
|
||||
{ name: 'Checklist', type: 'checkListItem', icon: ListChecks },
|
||||
{ name: 'Code Block', type: 'codeBlock', icon: Code2 },
|
||||
{ name: 'Code Block', type: 'codeBlock', icon: CodeBlock },
|
||||
]
|
||||
|
||||
const TOLARIA_SLASH_MENU_SUPPORT_SUBTEXT: Partial<Record<string, string>> = {
|
||||
heading: 'Markdown-safe heading (`#`). Persists after save and note switches.',
|
||||
heading_2: 'Markdown-safe heading (`##`). Persists after save and note switches.',
|
||||
heading_3: 'Markdown-safe heading (`###`). Persists after save and note switches.',
|
||||
heading_4: 'Markdown-safe heading (`####`). Persists after save and note switches.',
|
||||
heading_5: 'Markdown-safe heading (`#####`). Persists after save and note switches.',
|
||||
heading_6: 'Markdown-safe heading (`######`). Persists after save and note switches.',
|
||||
quote: 'Markdown-safe block quote (`>`). Persists after save and note switches.',
|
||||
bullet_list: 'Markdown-safe bullet list (`-`). Persists after save and note switches.',
|
||||
numbered_list: 'Markdown-safe numbered list (`1.`). Persists after save and note switches.',
|
||||
check_list: 'Markdown-safe checklist (`- [ ]`). Persists after save and note switches.',
|
||||
paragraph: 'Plain markdown paragraph text. Persists after save and note switches.',
|
||||
code_block: 'Markdown-safe fenced code block (```...```). Persists after save and note switches.',
|
||||
const TOLARIA_SLASH_MENU_ICONS: Partial<Record<string, PhosphorIcon>> = {
|
||||
audio: SpeakerHigh,
|
||||
bullet_list: ListBullets,
|
||||
check_list: ListChecks,
|
||||
code_block: CodeBlock,
|
||||
divider: Minus,
|
||||
emoji: Smiley,
|
||||
file: File,
|
||||
heading: TextHOne,
|
||||
heading_2: TextHTwo,
|
||||
heading_3: TextHThree,
|
||||
image: ImageSquare,
|
||||
numbered_list: ListNumbers,
|
||||
paragraph: Paragraph,
|
||||
quote: Quotes,
|
||||
table: Table,
|
||||
toggle_heading: TextHOne,
|
||||
toggle_heading_2: TextHTwo,
|
||||
toggle_heading_3: TextHThree,
|
||||
toggle_list: ListBullets,
|
||||
video: Video,
|
||||
}
|
||||
|
||||
function createTolariaSlashMenuIcon(Icon: PhosphorIcon) {
|
||||
return createElement(
|
||||
'span',
|
||||
{ className: 'tolaria-slash-menu-icon' },
|
||||
createElement(Icon, {
|
||||
'aria-hidden': true,
|
||||
className: 'tolaria-slash-menu-icon__regular',
|
||||
size: 18,
|
||||
weight: 'regular',
|
||||
}),
|
||||
createElement(Icon, {
|
||||
'aria-hidden': true,
|
||||
className: 'tolaria-slash-menu-icon__fill',
|
||||
size: 18,
|
||||
weight: 'fill',
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export function getTolariaBlockTypeSelectItems() {
|
||||
@@ -91,11 +128,12 @@ export function filterTolariaSlashMenuItems<T extends TolariaSlashMenuItem>(
|
||||
return items
|
||||
.filter((item) => !UNSUPPORTED_SLASH_MENU_KEYS.has(item.key))
|
||||
.map((item) => {
|
||||
const tolariaSubtext = TOLARIA_SLASH_MENU_SUPPORT_SUBTEXT[item.key]
|
||||
if (!tolariaSubtext) return item
|
||||
const TolariaIcon = TOLARIA_SLASH_MENU_ICONS[item.key]
|
||||
|
||||
return {
|
||||
...item,
|
||||
subtext: tolariaSubtext,
|
||||
icon: TolariaIcon ? createTolariaSlashMenuIcon(TolariaIcon) : item.icon,
|
||||
subtext: undefined,
|
||||
}
|
||||
}) as T[]
|
||||
}
|
||||
|
||||
@@ -22,4 +22,29 @@ describe('useBuildNumber', () => {
|
||||
const { result } = renderHook(() => useBuildNumber())
|
||||
await waitFor(() => expect(result.current).toBe('b?'))
|
||||
})
|
||||
|
||||
it('ignores build number requests that settle after unmount', async () => {
|
||||
const { mockInvoke } = await import('../mock-tauri')
|
||||
let rejectBuildNumber!: (reason?: unknown) => void
|
||||
vi.mocked(mockInvoke).mockReturnValueOnce(
|
||||
new Promise((_resolve, reject) => {
|
||||
rejectBuildNumber = reject
|
||||
}),
|
||||
)
|
||||
|
||||
const { result, unmount } = renderHook(() => useBuildNumber())
|
||||
unmount()
|
||||
|
||||
const originalWindow = globalThis.window
|
||||
try {
|
||||
vi.stubGlobal('window', undefined)
|
||||
rejectBuildNumber(new Error('late failure'))
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
} finally {
|
||||
vi.stubGlobal('window', originalWindow)
|
||||
}
|
||||
|
||||
expect(result.current).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,9 +10,19 @@ export function useBuildNumber(): string | undefined {
|
||||
const [buildNumber, setBuildNumber] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
tauriCall<string>('get_build_number').then(setBuildNumber).catch(() => {
|
||||
setBuildNumber('b?')
|
||||
})
|
||||
let mounted = true
|
||||
|
||||
tauriCall<string>('get_build_number')
|
||||
.then((value) => {
|
||||
if (mounted) setBuildNumber(value)
|
||||
})
|
||||
.catch(() => {
|
||||
if (mounted) setBuildNumber('b?')
|
||||
})
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
return buildNumber
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { test, expect, type Page } from '@playwright/test'
|
||||
import { test, expect, type Locator, type Page } from '@playwright/test'
|
||||
import { createFixtureVaultCopy, openFixtureVault, removeFixtureVaultCopy } from '../helpers/fixtureVault'
|
||||
|
||||
let tempVaultDir: string
|
||||
@@ -9,6 +9,104 @@ async function openAlphaProject(page: Page) {
|
||||
await expect(page.locator('.bn-editor')).toBeVisible({ timeout: 10_000 })
|
||||
}
|
||||
|
||||
async function openSlashMenu(page: Page) {
|
||||
await page.locator('.bn-editor').click()
|
||||
await page.keyboard.type('/')
|
||||
|
||||
const menu = page.locator('.bn-suggestion-menu')
|
||||
await expect(menu).toBeVisible({ timeout: 5_000 })
|
||||
return menu
|
||||
}
|
||||
|
||||
async function readMenuStyles(menu: Locator) {
|
||||
return menu.evaluate((node) => {
|
||||
const style = getComputedStyle(node)
|
||||
return {
|
||||
backgroundColor: style.backgroundColor,
|
||||
borderRadius: style.borderRadius,
|
||||
boxShadow: style.boxShadow,
|
||||
spacing: style.getPropertyValue('--mantine-spacing-sm').trim(),
|
||||
radius: style.getPropertyValue('--mantine-radius-default').trim(),
|
||||
shadow: style.getPropertyValue('--mantine-shadow-md').trim(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function expectMenuChrome(styles: Awaited<ReturnType<typeof readMenuStyles>>) {
|
||||
expect(styles.backgroundColor).not.toBe('rgba(0, 0, 0, 0)')
|
||||
expect(styles.borderRadius).not.toBe('0px')
|
||||
expect(styles.boxShadow).not.toBe('none')
|
||||
expect(styles.spacing).not.toBe('')
|
||||
expect(styles.radius).not.toBe('')
|
||||
expect(styles.shadow).not.toBe('')
|
||||
}
|
||||
|
||||
async function readSlashMenuItemStyles(item: Locator) {
|
||||
return item.evaluate((node) => {
|
||||
const item = node as HTMLElement
|
||||
const leftSection = item.querySelector<HTMLElement>(
|
||||
'.bn-mt-suggestion-menu-item-section[data-position="left"]',
|
||||
)!
|
||||
const shortcut = item.querySelector<HTMLElement>('.mantine-Badge-root')!
|
||||
const shortcutLabel = item.querySelector<HTMLElement>('.mantine-Badge-label')!
|
||||
const subtitle = item.querySelector<HTMLElement>('.bn-mt-suggestion-menu-item-subtitle')!
|
||||
const regularIcon = item.querySelector<HTMLElement>('.tolaria-slash-menu-icon__regular')!
|
||||
const fillIcon = item.querySelector<HTMLElement>('.tolaria-slash-menu-icon__fill')!
|
||||
const probe = document.createElement('span')
|
||||
probe.style.color = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--text-muted')
|
||||
.trim()
|
||||
document.body.appendChild(probe)
|
||||
const textMuted = getComputedStyle(probe).color
|
||||
probe.remove()
|
||||
|
||||
return {
|
||||
fillOpacity: getComputedStyle(fillIcon).opacity,
|
||||
itemHeight: getComputedStyle(item).height,
|
||||
leftBackgroundColor: getComputedStyle(leftSection).backgroundColor,
|
||||
leftBorderRadius: getComputedStyle(leftSection).borderRadius,
|
||||
leftPadding: getComputedStyle(leftSection).padding,
|
||||
regularOpacity: getComputedStyle(regularIcon).opacity,
|
||||
shortcutBackgroundColor: getComputedStyle(shortcut).backgroundColor,
|
||||
shortcutBorderRadius: getComputedStyle(shortcut).borderRadius,
|
||||
shortcutColor: getComputedStyle(shortcutLabel).color,
|
||||
shortcutFontSize: getComputedStyle(shortcutLabel).fontSize,
|
||||
shortcutPadding: getComputedStyle(shortcut).padding,
|
||||
subtitleDisplay: getComputedStyle(subtitle).display,
|
||||
subtitleText: subtitle.textContent ?? '',
|
||||
textMuted,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function expectSimplifiedItemStyles(
|
||||
styles: Awaited<ReturnType<typeof readSlashMenuItemStyles>>,
|
||||
) {
|
||||
expect(styles.itemHeight).toBe('34px')
|
||||
expect(styles.leftBackgroundColor).toBe('rgba(0, 0, 0, 0)')
|
||||
expect(styles.leftBorderRadius).toBe('0px')
|
||||
expect(styles.leftPadding).toBe('0px')
|
||||
expect(styles.shortcutBackgroundColor).toBe('rgba(0, 0, 0, 0)')
|
||||
expect(styles.shortcutBorderRadius).toBe('0px')
|
||||
expect(styles.shortcutColor).toBe(styles.textMuted)
|
||||
expect(styles.shortcutFontSize).toBe('10px')
|
||||
expect(styles.shortcutPadding).toBe('0px')
|
||||
expect(styles.subtitleDisplay).toBe('none')
|
||||
expect(styles.subtitleText).toBe('')
|
||||
}
|
||||
|
||||
async function readSlashMenuIconOpacities(item: Locator) {
|
||||
return item.evaluate((node) => {
|
||||
const item = node as HTMLElement
|
||||
const regularIcon = item.querySelector<HTMLElement>('.tolaria-slash-menu-icon__regular')!
|
||||
const fillIcon = item.querySelector<HTMLElement>('.tolaria-slash-menu-icon__fill')!
|
||||
return {
|
||||
fillOpacity: getComputedStyle(fillIcon).opacity,
|
||||
regularOpacity: getComputedStyle(regularIcon).opacity,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
test.describe('BlockNote slash menu styling', () => {
|
||||
test.beforeEach(() => {
|
||||
tempVaultDir = createFixtureVaultCopy()
|
||||
@@ -21,29 +119,22 @@ test.describe('BlockNote slash menu styling', () => {
|
||||
test('slash menu keeps Mantine styling tokens in the editor', async ({ page }) => {
|
||||
await openAlphaProject(page)
|
||||
|
||||
await page.locator('.bn-editor').click()
|
||||
await page.keyboard.type('/')
|
||||
const menu = await openSlashMenu(page)
|
||||
expectMenuChrome(await readMenuStyles(menu))
|
||||
await expect(menu.getByText('Subheadings', { exact: true })).toHaveCount(0)
|
||||
await expect(menu.getByText(/^Heading [4-6]$/)).toHaveCount(0)
|
||||
const secondItem = menu.locator('.bn-suggestion-menu-item').nth(1)
|
||||
await expect(secondItem.locator('.tolaria-slash-menu-icon')).toBeVisible()
|
||||
|
||||
const menu = page.locator('.bn-suggestion-menu')
|
||||
await expect(menu).toBeVisible({ timeout: 5_000 })
|
||||
const itemStyles = await readSlashMenuItemStyles(secondItem)
|
||||
expectSimplifiedItemStyles(itemStyles)
|
||||
expect(itemStyles.regularOpacity).toBe('1')
|
||||
expect(itemStyles.fillOpacity).toBe('0')
|
||||
|
||||
const menuStyles = await menu.evaluate((node) => {
|
||||
const style = getComputedStyle(node)
|
||||
return {
|
||||
backgroundColor: style.backgroundColor,
|
||||
borderRadius: style.borderRadius,
|
||||
boxShadow: style.boxShadow,
|
||||
spacing: style.getPropertyValue('--mantine-spacing-sm').trim(),
|
||||
radius: style.getPropertyValue('--mantine-radius-default').trim(),
|
||||
shadow: style.getPropertyValue('--mantine-shadow-md').trim(),
|
||||
}
|
||||
await secondItem.hover()
|
||||
expect(await readSlashMenuIconOpacities(secondItem)).toEqual({
|
||||
fillOpacity: '1',
|
||||
regularOpacity: '0',
|
||||
})
|
||||
|
||||
expect(menuStyles.backgroundColor).not.toBe('rgba(0, 0, 0, 0)')
|
||||
expect(menuStyles.borderRadius).not.toBe('0px')
|
||||
expect(menuStyles.boxShadow).not.toBe('none')
|
||||
expect(menuStyles.spacing).not.toBe('')
|
||||
expect(menuStyles.radius).not.toBe('')
|
||||
expect(menuStyles.shadow).not.toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user