refactor(vue): use test id helper for dynamic attrs

This commit is contained in:
Danila Poyarkov 2026-05-15 12:28:50 +03:00
parent cb1c52521b
commit 50a4e1bf93
13 changed files with 49 additions and 31 deletions

View file

@ -19,7 +19,7 @@ import {
import IconChevronRight from '~icons/lucide/chevron-right'
import { useI18n } from '@open-pencil/vue'
import { testId as testIdAttr, useI18n } from '@open-pencil/vue'
import { useMenuUI } from '@/components/ui/menu'
import { IS_TAURI } from '@/constants'
import { useAppMenu } from '@/app/shell/menu/app-menu'
@ -91,7 +91,7 @@ const subMenuCls = useMenuUI({ content: 'min-w-44' })
<MenubarRoot class="scrollbar-none flex items-center gap-0.5 overflow-x-auto">
<MenubarMenu v-for="menu in topMenus" :key="menu.label">
<MenubarTrigger
:data-test-id="`menubar-${menu.label.toLowerCase()}`"
v-bind="testIdAttr(`menubar-${menu.label.toLowerCase()}`)"
class="flex cursor-pointer items-center rounded px-2 py-1 text-xs text-muted transition-colors select-none hover:bg-hover hover:text-surface data-[state=open]:bg-hover data-[state=open]:text-surface"
>
{{ menu.label }}

View file

@ -8,7 +8,13 @@ import {
ContextMenuSubContent,
ContextMenuPortal
} from 'reka-ui'
import { useEditorCommands, useI18n, useMenuModel, useSelectionState } from '@open-pencil/vue'
import {
testId as testIdAttr,
useEditorCommands,
useI18n,
useMenuModel,
useSelectionState
} from '@open-pencil/vue'
import { useEditorStore } from '@/app/editor/active-store'
import { createCanvasMenuActions } from '@/app/editor/canvas/menu-actions'
@ -120,7 +126,7 @@ const contextCommandTestIds: Record<string, string> = {
</ContextMenuSub>
<ContextMenuItem
v-else
:data-test-id="item.id ? contextCommandTestIds[item.id] : undefined"
v-bind="testIdAttr(item.id ? contextCommandTestIds[item.id] : undefined)"
:class="canvasMenuItemClass(item.label, cls)"
:disabled="item.disabled"
@select="item.action?.()"

View file

@ -1,12 +1,14 @@
<script setup lang="ts">
import { useAttrs } from 'vue'
import { ScrubInputRoot, ScrubInputField, ScrubInputDisplay } from '@open-pencil/vue'
import { computed, useAttrs } from 'vue'
import { ScrubInputRoot, ScrubInputField, ScrubInputDisplay, testId } from '@open-pencil/vue'
import { useEditorStore } from '@/app/editor/active-store'
const attrs = useAttrs()
const store = useEditorStore()
const rootTestId = computed(() => (attrs['data-test-id'] as string | undefined) ?? 'scrub-input')
const { modelValue, min, max, step, icon, label, suffix, sensitivity, placeholder } = defineProps<{
modelValue: number | symbol
min?: number
@ -47,8 +49,7 @@ defineOptions({ inheritAttrs: false })
"
>
<div
v-bind="attrs"
:data-test-id="(attrs['data-test-id'] as string | undefined) ?? 'scrub-input'"
v-bind="{ ...attrs, ...testId(rootTestId) }"
:tabindex="editing ? undefined : 0"
:class="[
attrs.class,

View file

@ -11,7 +11,7 @@ import IconChevronDown from '~icons/lucide/chevron-down'
import { menu } from '@/components/ui/menu'
import ToolButton from '@/components/Toolbar/ToolButton.vue'
import { ToolbarItem } from '@open-pencil/vue'
import { testId as testIdAttr, ToolbarItem } from '@open-pencil/vue'
import type { Tool } from '@open-pencil/vue'
import type { EditorToolDef } from '@open-pencil/core/editor'
@ -69,7 +69,7 @@ function activeKeyForTool() {
<DropdownMenuRoot>
<DropdownMenuTrigger as-child>
<button
:data-test-id="`${mobile ? 'mobile-' : ''}toolbar-flyout-${tool.key.toLowerCase()}`"
v-bind="testIdAttr(`${mobile ? 'mobile-' : ''}toolbar-flyout-${tool.key.toLowerCase()}`)"
class="flex h-8 w-3 cursor-pointer items-center justify-center border-none transition-colors"
:class="[
mobile ? 'rounded-[6px] select-none' : 'rounded-lg',
@ -93,7 +93,7 @@ function activeKeyForTool() {
:tool="sub"
>
<DropdownMenuItem
:data-test-id="`${mobile ? 'mobile-' : ''}toolbar-flyout-item-${sub.toLowerCase()}`"
v-bind="testIdAttr(`${mobile ? 'mobile-' : ''}toolbar-flyout-item-${sub.toLowerCase()}`)"
:class="menu().item({ class: subActive ? 'bg-accent text-white' : undefined })"
@select="actions.select"
>

View file

@ -1,4 +1,6 @@
<script setup lang="ts">
import { testId as testIdAttr } from '@open-pencil/vue'
import type { ToolbarActionItem } from '@/components/Toolbar/types'
const { actions, testPrefix } = defineProps<{
@ -15,7 +17,7 @@ const emit = defineEmits<{
<button
v-for="item in actions"
:key="item.label"
:data-test-id="`${testPrefix}-${item.label.toLowerCase()}`"
v-bind="testIdAttr(`${testPrefix}-${item.label.toLowerCase()}`)"
class="flex size-8 cursor-pointer items-center justify-center rounded-[6px] border-none bg-transparent text-muted transition-colors select-none active:bg-hover active:text-surface"
@click="emit('action', item)"
>

View file

@ -27,7 +27,7 @@ import {
} from 'reka-ui'
import { FlexRender } from '@tanstack/vue-table'
import { useI18n, useVariablesEditor } from '@open-pencil/vue'
import { testId as testIdAttr, useI18n, useVariablesEditor } from '@open-pencil/vue'
import IconHash from '~icons/lucide/hash'
import IconPalette from '~icons/lucide/palette'
@ -390,7 +390,7 @@ function modeId(columnId: string): string {
v-for="item in variableTypes"
:key="item.type"
:class="menuCls.item"
:data-test-id="`variables-add-${item.type.toLowerCase()}`"
v-bind="testIdAttr(`variables-add-${item.type.toLowerCase()}`)"
@select="ctx.addVariable(item.type)"
>
<component :is="variableTypeIcons[item.type]" :class="menuCls.icon" />

View file

@ -10,6 +10,7 @@ import {
AlertDialogTitle
} from 'reka-ui'
import { computed } from 'vue'
import { testId as testIdAttr } from '@open-pencil/vue'
import {
currentPermission,
@ -85,7 +86,7 @@ function handleDismiss() {
<AlertDialogAction
v-for="opt in allowOptions"
:key="opt.optionId"
:data-test-id="`acp-permission-option-${opt.kind}`"
v-bind="testIdAttr(`acp-permission-option-${opt.kind}`)"
class="w-full rounded bg-accent px-3 py-1.5 text-xs font-medium text-white hover:bg-accent/90"
@click="respondToPermission(opt.optionId)"
>
@ -95,7 +96,7 @@ function handleDismiss() {
<AlertDialogCancel
v-for="opt in rejectOptions"
:key="opt.optionId"
:data-test-id="`acp-permission-option-${opt.kind}`"
v-bind="testIdAttr(`acp-permission-option-${opt.kind}`)"
class="w-full rounded border border-border bg-canvas px-3 py-1.5 text-xs text-muted hover:bg-hover hover:text-surface"
@click="respondToPermission(opt.optionId)"
>

View file

@ -2,6 +2,7 @@
import { isTextUIPart, isToolUIPart, getToolName } from 'ai'
import { CollapsibleContent, CollapsibleRoot, CollapsibleTrigger } from 'reka-ui'
import { Markdown } from 'vue-stream-markdown'
import { testId as testIdAttr } from '@open-pencil/vue'
import 'vue-stream-markdown/index.css'
import type { UIDataTypes, UIMessage, UIMessagePart, UITools } from 'ai'
@ -40,7 +41,7 @@ function partKey(part: UIMessagePart<UIDataTypes, UITools>, index: number): stri
<template>
<div
:data-test-id="`chat-message-${message.role}`"
v-bind="testIdAttr(`chat-message-${message.role}`)"
:class="message.role === 'user' ? 'flex justify-end' : ''"
>
<div class="min-w-0 space-y-1.5" :class="message.role === 'user' ? 'max-w-[85%]' : ''">

View file

@ -4,7 +4,7 @@ import BoundVariableButton from '@/components/properties/BoundVariableButton.vue
import VariablePickerPopover from '@/components/properties/VariablePickerPopover.vue'
import { useIconButtonUI } from '@/components/ui/icon-button'
import { useI18n } from '@open-pencil/vue'
import { testId as testIdAttr, useI18n } from '@open-pencil/vue'
import {
opacityFromPercent,
@ -99,7 +99,7 @@ const { panels, dialogs } = useI18n()
/>
<button
:data-test-id="visibilityTestId"
v-bind="testIdAttr(visibilityTestId)"
:data-visible="item.visible ? 'true' : 'false'"
class="shrink-0 cursor-pointer border-none bg-transparent p-0 text-muted hover:text-surface"
@click="emit('toggleVisibility')"

View file

@ -4,7 +4,7 @@ import ColorInput from '@/components/ColorPicker/ColorInput.vue'
import ScrubInput from '@/components/ScrubInput.vue'
import { useIconButtonUI } from '@/components/ui/icon-button'
import { useSectionUI } from '@/components/ui/section'
import { PropertyListRoot, useEffectsControls, useI18n } from '@open-pencil/vue'
import { PropertyListRoot, testId as testIdAttr, useEffectsControls, useI18n } from '@open-pencil/vue'
import { colorToCSS } from '@open-pencil/core/color'
@ -65,7 +65,7 @@ const sectionCls = useSectionUI()
/>
<button
:data-test-id="`effect-visibility-${i}`"
v-bind="testIdAttr(`effect-visibility-${i}`)"
:data-visible="effect.visible ? 'true' : 'false'"
class="cursor-pointer border-none bg-transparent p-0 text-muted hover:text-surface"
@click="actions.toggleVisibility(i)"

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import Tip from '@/components/ui/Tip.vue'
import { useI18n, useLayoutControlsContext } from '@open-pencil/vue'
import { testId as testIdAttr, useI18n, useLayoutControlsContext } from '@open-pencil/vue'
import type { LayoutMode } from '@open-pencil/core/scene-graph'
@ -42,7 +42,7 @@ const layoutModes: { mode: LayoutMode; test: string }[] = [
<button
v-for="dir in layoutModes"
:key="dir.mode"
:data-test-id="`layout-direction-${dir.test}`"
v-bind="testIdAttr(`layout-direction-${dir.test}`)"
class="flex cursor-pointer items-center justify-center rounded border px-2 py-1"
:class="
(dir.mode === 'GRID' ? ctx.isGrid : ctx.node.layoutMode === dir.mode)

View file

@ -17,7 +17,12 @@ import VariableScrubInput from '@/components/properties/VariableScrubInput.vue'
import BoundVariableButton from '@/components/properties/BoundVariableButton.vue'
import VariablePickerPopover from '@/components/properties/VariablePickerPopover.vue'
import { useSelectUI } from '@/components/ui/select'
import { useI18n, useLayoutControlsContext, useNumberVariableBinding } from '@open-pencil/vue'
import {
testId as testIdAttr,
useI18n,
useLayoutControlsContext,
useNumberVariableBinding
} from '@open-pencil/vue'
import type { LayoutSizing } from '@open-pencil/core/scene-graph'
import type { SizeLimitProp, TestId } from '@open-pencil/vue'
@ -341,7 +346,7 @@ function handleSizeSelect(axis: 'width' | 'height', value: SizeSelectValue) {
<div :ref="limitFieldRefs.set" class="min-w-0">
<VariableScrubInput
v-if="ctx.node"
:data-test-id="item.testId"
v-bind="testIdAttr(item.testId)"
:icon="item.icon()"
:model-value="Math.round(item.value() ?? 0)"
:min="0"
@ -356,7 +361,7 @@ function handleSizeSelect(axis: 'width' | 'height', value: SizeSelectValue) {
@update:model-value="(value) => handleLimitSelect(item.prop, value as string)"
>
<SelectTrigger
:data-test-id="`${item.testId}-menu`"
v-bind="testIdAttr(`${item.testId}-menu`)"
:reference="limitFieldAnchor(index)"
class="flex shrink-0 cursor-pointer items-center self-stretch border-none bg-transparent px-1 text-[11px] text-muted outline-none"
@pointerdown.stop
@ -385,7 +390,7 @@ function handleSizeSelect(axis: 'width' | 'height', value: SizeSelectValue) {
</VariableScrubInput>
<ScrubInput
v-else
:data-test-id="item.testId"
v-bind="testIdAttr(item.testId)"
:icon="item.icon()"
:model-value="Math.round(item.value() ?? 0)"
:min="0"
@ -398,7 +403,7 @@ function handleSizeSelect(axis: 'width' | 'height', value: SizeSelectValue) {
@update:model-value="(value) => handleLimitSelect(item.prop, value as string)"
>
<SelectTrigger
:data-test-id="`${item.testId}-menu`"
v-bind="testIdAttr(`${item.testId}-menu`)"
:reference="limitFieldAnchor(index)"
class="flex shrink-0 cursor-pointer items-center self-stretch border-none bg-transparent px-1 text-[11px] text-muted outline-none"
@pointerdown.stop

View file

@ -12,6 +12,8 @@ import {
import { computed, nextTick, ref, watch } from 'vue'
import { testId as testIdAttr } from '@open-pencil/vue'
import type { Variable } from '@open-pencil/core/scene-graph'
const searchTerm = defineModel<string>('searchTerm', { default: '' })
@ -77,7 +79,7 @@ function submitCreate() {
<template>
<PopoverRoot v-model:open="open">
<PopoverTrigger
:data-test-id="triggerTestId"
v-bind="testIdAttr(triggerTestId)"
:aria-label="triggerLabel"
:title="triggerLabel"
class="shrink-0 cursor-pointer border-none bg-transparent p-0 text-muted hover:text-surface"
@ -144,7 +146,7 @@ function submitCreate() {
class="min-w-0 flex-1 rounded border border-border bg-transparent px-1.5 py-1 text-[11px] text-surface outline-none placeholder:text-muted focus:border-accent"
/>
<button
:data-test-id="createTestId"
v-bind="testIdAttr(createTestId)"
:disabled="!canCreate"
class="rounded border border-border bg-panel px-1.5 py-1 text-[11px] text-surface hover:bg-hover disabled:cursor-not-allowed disabled:opacity-50"
type="submit"
@ -154,7 +156,7 @@ function submitCreate() {
</form>
<button
v-else
:data-test-id="createTestId"
v-bind="testIdAttr(createTestId)"
class="flex w-full cursor-pointer items-center gap-1.5 bg-transparent px-2 py-1.5 text-left text-[11px] text-muted hover:bg-hover hover:text-surface"
@click="startCreate"
>