diff --git a/apps/admin/e2e/embed-slots.spec.ts b/apps/admin/e2e/embed-slots.spec.ts index 0d1d399..f2b78fb 100644 --- a/apps/admin/e2e/embed-slots.spec.ts +++ b/apps/admin/e2e/embed-slots.spec.ts @@ -224,12 +224,21 @@ test.describe.serial('embed slot management (P4b Task 5)', () => { await expect(page.getByTestId('slot-form-interval')).toHaveCount(0) await expect(page.getByTestId('slot-form-size')).toHaveCount(0) - // 真实组件预览(§8.2):iframe 挂载 + 模拟点赞按钮在工具区。 + // 真实组件预览(§8.2):iframe 挂载 + 模拟点赞按钮在工具区;预览帧内为 + // 真实 EmbedStream(.stream-viewport)且模拟点赞出心形(WYSIWYG 证据)。 await expect(page.getByTestId('embed-preview-v2-iframe')).toBeVisible() await expect(page.getByTestId('embed-preview-sim-like')).toBeVisible() + const previewFrame = page + .frames() + .find((f) => f.url().includes('/embed-preview')) + expect(previewFrame).toBeDefined() + await expect(previewFrame!.locator('.stream-viewport')).toHaveCount(1) + await page.getByTestId('embed-preview-sim-like').click() + await expect(previewFrame!.locator('.heart').first()).toBeVisible({ timeout: 5_000 }) // 速度调整(24 px/s):滑轨点击定位((24-8)/(32-8)*180px ≈ 120)后断言值。 - await page.getByTestId('slot-form-speed').click({ position: { x: 120, y: 10 } }) + const runway = page.getByTestId('slot-form-speed').locator('.el-slider__runway') + await runway.click({ position: { x: 120, y: 4 } }) await expect(page.getByTestId('slot-form-speed-value')).toContainText('24') await page.getByTestId('slot-form-submit').click() await expect(page.getByTestId('slot-form-dialog')).toBeHidden() diff --git a/apps/viewer/src/pages/EmbedPreviewPage.vue b/apps/viewer/src/pages/EmbedPreviewPage.vue new file mode 100644 index 0000000..53781b4 --- /dev/null +++ b/apps/viewer/src/pages/EmbedPreviewPage.vue @@ -0,0 +1,122 @@ + + + + + + + + diff --git a/apps/viewer/src/pages/embed/EmbedStream.vue b/apps/viewer/src/pages/embed/EmbedStream.vue index 7917b25..dbbd477 100644 --- a/apps/viewer/src/pages/embed/EmbedStream.vue +++ b/apps/viewer/src/pages/embed/EmbedStream.vue @@ -33,6 +33,9 @@ const props = defineProps<{ /** 组件活动闸门(收起/隐藏/不可见/gone → false,停滚动与轮询)。 */ active: boolean; reducedMotion: boolean; + /** 管理预览模式(E06 §8.2):禁用真实 WS 订阅(隔离——不上报/不提交/ + * 不连真实相册房间);模拟事件经 simulateBurst 注入真实校验管线。 */ + preview?: boolean; }>(); const emit = defineEmits<{ @@ -111,8 +114,11 @@ const firstKeyByPhoto = computed(() => { }); // ── E05 气泡:真实 burst → 节流合并 → 照片交集下侧出泡(§5.2)── +// 预览模式禁用真实订阅(§8.2 隔离:不连相册房间;模拟事件走 ingestForTest)。 const albumTokenRef = computed(() => props.albumToken); -const reactionsEnabledRef = computed(() => props.reactions && props.albumToken !== null); +const reactionsEnabledRef = computed( + () => props.reactions && props.albumToken !== null && props.preview !== true, +); const activeRef = computed(() => props.active); const reactions = useEmbedReactions({ @@ -131,6 +137,23 @@ const reactions = useEmbedReactions({ }, }); +/** 管理预览模拟点赞入口(E06 §8.2「事件明确是测试模拟」):经真实校验 + * 管线(去重/节流/可见归属/抽样)注入,不触网络。 */ +function simulateBurst(photoId: string, delta: number): void { + reactions.ingestForTest({ + type: 'likes.burst', + photoId, + albumId: 'preview', + delta, + total: 0, + countVersion: 0, + burstId: `preview-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + originBatchIds: [], + }); +} + +defineExpose({ simulateBurst }); + // 收起/隐藏/换绑/减少动效 → 清粒子(§5.3)。 watch(activeRef, (value) => { if (!value) layerEl.value?.clear(); @@ -383,8 +406,9 @@ onBeforeUnmount(() => {