Files
Gnuboard7/tests/scenarios/layout-editor-element-placement.yaml
T
2026-07-01 10:30:32 +09:00

197 lines
14 KiB
YAML

feature: 레이아웃 편집기 요소 배치 (팔레트 / 컨텍스트 + 버튼 / 드래그앤드롭 / 오버레이 / 저장)
phase: 3
session:
issue: 383
axes:
insertion_path: [palette_open, context_plus_button, drag_drop, duplicate_via_context_menu]
container_layout: [block, flex_row_single, flex_row_wrap, flex_column_single]
target_kind: [container_div, container_p, container_span, container_table, leaf_img, composite_settings, composite_no_settings, sortable_node]
dragged_kind: [div, p, span, img, composite, button]
nesting_decision: [allowed, denied_no_draggable, denied_no_accepts, denied_into_data_bound, denied_into_base_locked, denied_into_extension_locked, denied_into_sortable]
selected_node_state: [empty_selection, basic_selected, composite_selected, base_locked_selected, partial_locked_selected, extension_locked_selected, extension_point_selected, data_bound_selected, iteration_instance_selected]
# 잠긴 노드 선택 시 "X 다음에 추가" 안전 anchor
locked_insert_anchor: [none_self, extension_self, base_self, partial_self, extension_point_self, iteration_definition_node]
click_target_action: [no_action, navigate_internal, navigate_dynamic, navigate_external, openModal, apiCall, a_href_internal, a_href_external]
link_edit_destination: [route_in_tree, route_not_in_tree, external_url, dynamic_path_with_binding]
history_action: [undo, redo, undo_after_save]
save_outcome: [valid_save, validation_fails_422, optimistic_concurrency]
id_lazy_assignment_trigger: [none, modal_target_reference, duplicate_with_id]
save_payload_origin: [route_only, route_with_base_sibling, route_with_extension_child, route_with_partial_sibling, extends_with_slots, response_lock_version_meta_leaked]
default_node_source: [editorSpecDefaultNode, componentsJsonDefault, requiresDefaultNode_blocked, missing_defaultNode_allowed_with_badge]
exclusions:
- { insertion_path: drag_drop, target_kind: sortable_node,
reason: "sortable 노드는 편집기 드래그 대상에서 제외 — 11.4.5 (b-2)" }
- { dragged_kind: composite, nesting_decision: allowed, target_kind: leaf_img,
reason: "Img.accepts=[] — composite 도 거부됨" }
- { click_target_action: a_href_external, link_edit_destination: route_in_tree,
reason: "외부 URL 은 트리 매칭 자체가 일어나지 않음" }
- { history_action: redo, save_outcome: optimistic_concurrency,
reason: "redo 는 in-session — 저장 동시성과 무관" }
effects:
- palette_filters_by_parent_accepts
- palette_groups_design_and_db_per_spec_componentPalette
- palette_inserts_via_default_node_skeleton
- palette_default_node_template_spec_takes_precedence_over_core_fallback
- palette_default_node_core_fallback_uses_components_json_only_no_visual_hints
- palette_blocks_when_requires_default_node_missing
- palette_label_uses_i18n_key
- default_node_text_uses_i18n_key
# S5a-3 후속 — defaultNode 의 공통 디자인 className 이 신규 노드에 1:1 복제 적용
- palette_default_node_className_replicated_into_inserted_node
- palette_default_node_className_reflects_template_design_system_tokens
- palette_default_node_button_omits_variant_size_uses_className_only
- palette_default_node_composite_keeps_sample_props_without_design_className
- palette_card_shows_missing_default_node_badge
- context_plus_button_block_shows_top_bottom_only
- context_plus_button_flex_row_single_shows_left_right_only_with_top_bottom_disabled
- context_plus_button_flex_wrap_shows_four_directions
- context_plus_button_flex_detected_by_computed_style_not_classname
- drag_handle_present_only_for_nesting_draggable_components
- drag_handle_absent_for_sortable_nodes
- drag_handle_absent_for_data_bound_nodes
- drag_handle_absent_for_base_origin_nodes_in_route_edit_mode
- drop_zone_resolver_finds_deepest_accepting_ancestor
- drop_zone_resolver_block_uses_y_midpoint_comparison
- drop_zone_resolver_flex_row_uses_x_midpoint_comparison
- drop_zone_resolver_flex_wrap_computes_2d_flow_index
- drop_indicator_renders_horizontal_line_in_block
- drop_indicator_renders_vertical_line_in_flex
- drop_overlay_renders_translucent_ghost_following_pointer
- drop_denied_area_shows_cursor_not_allowed
- drop_into_sortable_internal_denied
- drop_into_data_bound_denied
- drop_into_extension_locked_denied
# S5b 드롭 슬롯 관련 레벨 한정 + 이동 후 선택 재복원
- drop_slots_scoped_to_dragged_ancestor_chain_and_sibling_nest_targets
- drop_slots_exclude_content_node_inner_descendants
- drop_slots_contents_wrapper_child_boundary_uses_descendant_union_rect
- drop_outer_container_row_reachable_via_ancestor_gap_slot
- drag_move_commits_single_node_without_dragging_siblings
- selection_follows_moved_node_to_new_path_after_drop
# 검수 10차 후속 — contents 래퍼 시각 흐름 투명화 + 채워진 카드 nest
- drop_slots_exclude_contents_wrapper_child_from_parent_boundary
- drop_slots_keep_non_contents_sibling_gaps_for_grid_placement
- drop_no_full_area_breakout_slot_at_contents_parent_container
- drop_nest_allowed_into_filled_accepting_container
- drag_card_inner_element_into_sibling_card_nest
- drop_card_beside_hero_grid_sibling_via_parent_gap_slot
- drop_slot_index_uses_original_tree_index_consistent_with_moveNode
# S5b 후속 — 컨테이너 간 이동 + 선택 기준 드래그
- drop_slots_span_all_accepting_containers
- handle_zindex_depth_ordered
- selection_based_drag_substitution
- child_reselect_under_selected_parent
- inner_child_nests_into_sibling_filled_card
# 회귀 — 어포던스(+/ⓘ/리사이즈) 클릭이 드래그 핸들 z-index 에 가로채이지 않음
- overlay_affordance_above_drag_handle_zindex
- overlay_affordance_click_not_intercepted_by_drag_handle
- drag_handle_depth_clamped_below_affordance_band
- drag_initiation_preserved_on_element_body_after_affordance_zindex_fix
# S5b 후속 — 네비 어포던스 / data_bound / 이터레이션 가상 묶음
- nav_destination_unreachable_affordance_hidden
- data_bound_node_draggable_and_selectable
- iteration_instance_not_individually_draggable
- iteration_virtual_group_select_move_ghost
- element_overlay_hover_dashed_box
- element_overlay_selected_solid_box_with_resize_handle_placeholders
- element_overlay_base_locked_shows_edit_common_layout_affordance
- element_overlay_extension_locked_shows_edit_extension_affordance
- context_menu_three_items_exactly_setting_duplicate_delete
- duplicate_preserves_id_absence_for_id_less_nodes
- duplicate_regenerates_id_for_id_bearing_nodes_to_avoid_collision
- canvas_click_does_not_trigger_navigate_in_edit_mode
- canvas_click_a_href_prevent_default_blocks_full_page_reload
- link_edit_affordance_internal_route_transitions_via_select_route_without_reload
- link_edit_affordance_external_or_unmatched_disabled_with_notice
- link_edit_affordance_dynamic_path_disabled_with_notice
- undo_redo_reverts_palette_insert_drag_move_delete_property_change_inline_edit
- undo_redo_history_lives_per_session_independent_of_version_history
- save_invokes_put_layouts_endpoint_and_creates_version
- save_returns_422_with_validation_errors_when_invalid
- dirty_guard_blocks_route_change_without_save_confirmation
- id_lazy_assignment_only_when_modal_target_or_duplicate_collision
- layout_extension_save_guard_blocks_inactive_extension_new_nodes
# 결함 I — 편집기 응답 메타 노드 저장 페이로드 마스킹 (클라이언트 1차 + 백엔드 2차)
- save_payload_strips_base_origin_nodes_client_side
- save_payload_strips_extension_origin_nodes_client_side
- save_payload_strips_partial_origin_nodes_client_side
- save_payload_preserves_route_origin_nodes_with_source_meta_removed
- save_payload_recurses_children_masking_for_route_nodes
- save_payload_drops_components_key_when_extends_present_and_keeps_masked_slots
- save_payload_drops_response_only_lock_version_meta_from_content
- save_payload_backend_2nd_guard_strips_inherited_nodes_when_client_skipped
# 결함 P — extends 자식 레이아웃 원본 보존 (응답 __editor.original 컨테이너)
- merge_response_attaches_editor_original_container_for_extends_child
- merge_response_attaches_editor_original_container_for_standalone_layout
- save_uses_editor_original_as_ssot_when_present
- save_falls_back_to_legacy_masking_when_editor_original_absent
- save_strips_editor_meta_container_from_payload
# 결함 — 빈 컨테이너 첫 자식 삽입은 그 컨테이너 자신의 accepts 로 필터
# (children 배열 존재가 아니라 nesting.containers 정의 기준 — 드롭 경로 isContainerComponent 와 정합)
- empty_container_global_palette_filters_by_own_accepts_not_parent
- save_strips_editor_meta_container_via_backend_2nd_guard_when_client_skipped
# 결함 Q — useLayoutDocument fetch URL 의 cache_version 동반 (stale 응답 회피)
- useLayoutDocument_fetch_appends_cache_version_query_for_editor_mode
- useLayoutDocument_fetch_matches_LayoutLoader_cache_version_pattern
# 결함 R — 편집 모드 응답에 자식 레이아웃 lock_version 동봉
- merge_response_top_level_lock_version_comes_from_child_layout_row_when_with_source_meta
- save_uses_top_level_lock_version_as_expected_lock_version_for_extends_child
- layout_extension_update_endpoint_keeps_separate_lock_version_response_path
# 결함 T / T-2 — 마스킹 정책 정정 (Path A/B 분기 + base 노드 children lifting)
- save_path_A_extends_with_slots_no_components_uses_slot_masking_only
- save_path_B_route_with_merged_components_extracts_route_content_from_merged_tree
- save_base_origin_node_is_lifted_so_its_children_survive_when_route_owned
- save_extension_origin_node_remains_fully_stripped_with_children
- save_partial_origin_node_wrapper_strips_but_lifts_inherited_slot_children
- editor_original_fallback_uses_plain_object_guard_for_string_array_null_meta
# 결함 U — 편집 모드 응답 캐시 무효화 누락 (clearLayoutCache + clearPublicServingCache 모두)
- clearLayoutCache_invalidates_with_source_meta_true_cache_key
- clearLayoutCache_invalidates_with_source_meta_false_cache_key
- clearPublicServingCache_invalidates_meta_suffix_cache_key
- clearPublicServingCache_invalidates_non_meta_cache_key
- save_followup_reload_returns_fresh_editor_response_with_new_lock_version
# 잠긴 영역에서도 "요소 추가"(+) 버튼 노출 + 묶음 바깥 형제 삽입
- locked_node_shows_insertion_plus_via_anchor_box
- insertion_anchor_self_for_extension_base_partial_extension_point
- insertion_anchor_iteration_definition_node_not_instance
- insertion_plus_renders_within_viewport_at_anchor_box_not_giant_parent
- insertion_inserts_outside_locked_group_as_sibling_of_anchor
- iteration_insertion_lands_after_repeat_group_not_beside_instance
- global_palette_locked_node_inserts_at_anchor_sibling_after
- added_node_in_locked_area_preserved_as_route_content_on_save
- none_node_insertion_unchanged_anchor_equals_self
test_files:
- resources/js/core/template-engine/layout-editor/__tests__/dnd/nestingRules.test.ts
- resources/js/core/template-engine/layout-editor/__tests__/dnd/dropSlots.test.ts
- resources/js/core/template-engine/layout-editor/__tests__/hooks/useInsertionPoints.test.ts
- resources/js/core/template-engine/layout-editor/__tests__/hooks/useEditorHistory.test.ts
- resources/js/core/template-engine/layout-editor/__tests__/hooks/useEditorHistory.devtools.test.ts
- resources/js/core/template-engine/layout-editor/__tests__/hooks/useElementSelection.test.ts
- resources/js/core/template-engine/layout-editor/__tests__/hooks/useLayoutDocument.saveGuard.test.ts
- resources/js/core/template-engine/layout-editor/__tests__/hooks/useCanvasDnd.test.ts
- resources/js/core/template-engine/layout-editor/__tests__/utils/layoutTreeUtils.test.ts
- resources/js/core/template-engine/layout-editor/__tests__/components/ComponentPalette.test.tsx
- resources/js/core/template-engine/layout-editor/__tests__/components/ElementOverlay.test.tsx
- resources/js/core/template-engine/layout-editor/__tests__/components/InsertionAffordances.test.tsx
- resources/js/core/template-engine/layout-editor/__tests__/components/ComponentContextMenu.test.tsx
- resources/js/core/template-engine/layout-editor/__tests__/components/DndCanvasLayer.test.tsx
- resources/js/core/template-engine/layout-editor/__tests__/layouts/canvas-click-action-intercept.test.tsx
- resources/js/core/template-engine/layout-editor/__tests__/layouts/link-edit-affordance.test.tsx
- resources/js/core/template-engine/layout-editor/__tests__/layouts/drag-drop-matrix.test.tsx
- tests/Feature/Api/Admin/LayoutControllerEditorSaveTest.php
# S5a-3 후속 — defaultNode 공통 디자인 className 복제 검증
- templates/_bundled/sirsoft-basic/__tests__/layouts/editor-spec-default-node.test.tsx
- templates/_bundled/sirsoft-admin_basic/__tests__/layouts/editor-spec-default-node.test.tsx
- tests/Playwright/specs/layout-editor/default-node-design.spec.ts
# S5b — 드래그 앤 드롭 재배치 E2E (dispatchDnd PointerEvent 시퀀스)
- tests/Playwright/specs/layout-editor/drag-drop-reorder.spec.ts
# 회귀 — 어포던스 클릭 가로채기 (z-index 계약) E2E
- tests/Playwright/specs/layout-editor/overlay-affordance-zindex.spec.ts
- resources/js/core/template-engine/layout-editor/__tests__/utils/overlayZIndex.test.ts
# 잠긴 영역 요소 추가(+) 복원 (anchor 박스 + 묶음 바깥 삽입) E2E
- tests/Playwright/specs/layout-editor/locked-area-insertion.spec.ts
notes: |
- dropZoneResolver/useCanvasDnd/DndCanvasLayer/drag-drop-matrix 등 dnd 본격 동작은 S5b (PR #4b) 에서 추가.
- link-edit-affordance.test.tsx 는 useElementSelection.test.ts 가 classifyNavAffordance 단위 분기를 커버하며,
SELECT_ROUTE 통합 동작은 LayoutEditorChrome 의 handleNavigateToDestination 단계에서 회귀.