mirror of
https://github.com/block/buzz.git
synced 2026-10-31 22:41:12 +00:00
Fix video comment effect wrapping (#5748)
## What changed - render video-review timecode chips inside the first Markdown paragraph so comment text wraps naturally around them - reuse the canonical video-review chip treatment across the timeline, Inbox previews, and Inbox detail - preserve video-review context in Inbox so timestamp chips remain interactive ## Why Video comments now support Markdown-like effects, but non-player surfaces rendered the timestamp beside a separate text layout. That kept the chip and comment from sharing the same inline flow and made Inbox behavior inconsistent with the player. ## Validation - `pnpm --dir desktop check` - 100 focused Markdown, timecode, video-review, and Inbox unit tests - `pnpm --dir desktop build:e2e` - focused `video-attachment.spec.ts` Playwright scenario - pre-push desktop typecheck and 4,761-test desktop suite - native Builderlab staging with the configured profile Focused timeline and Inbox snapshots will be attached in a PR comment. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz> Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import React from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
import { clearMarkdownNodeCache, renderCachedMarkdown } from "./nodeCache.ts";
|
||||
@@ -81,6 +82,103 @@ test("render variants do not collide", () => {
|
||||
assert.notEqual(interactive, nonInteractive);
|
||||
});
|
||||
|
||||
test("leading inline content is inserted into the first prose-capable block", () => {
|
||||
const components = {
|
||||
span: ({ children, node: _node, ...props }) =>
|
||||
"data-leading-inline-content" in props
|
||||
? React.createElement(
|
||||
"button",
|
||||
{ "data-chip": "", type: "button" },
|
||||
"00:01",
|
||||
)
|
||||
: React.createElement("span", props, children),
|
||||
};
|
||||
|
||||
for (const [content, pattern] of [
|
||||
["plain note", /<p><button[^>]*>00:01<\/button>plain note<\/p>/],
|
||||
["> quoted note", /<blockquote>\s*<p><button[^>]*>00:01<\/button>quoted/],
|
||||
["- list note", /<li><button[^>]*>00:01<\/button>list note<\/li>/],
|
||||
["# heading", /<h1><button[^>]*>00:01<\/button>heading<\/h1>/],
|
||||
]) {
|
||||
const node = renderCachedMarkdown({
|
||||
...BASE,
|
||||
components,
|
||||
content,
|
||||
leadingInlineContent: true,
|
||||
variant: "leading",
|
||||
});
|
||||
assert.match(renderToStaticMarkup(node), pattern);
|
||||
}
|
||||
});
|
||||
|
||||
test("leading inline content falls back before code and media blocks", () => {
|
||||
const components = {
|
||||
span: ({ children, node: _node, ...props }) =>
|
||||
"data-leading-inline-content" in props
|
||||
? React.createElement(
|
||||
"button",
|
||||
{ "data-chip": "", type: "button" },
|
||||
"00:01",
|
||||
)
|
||||
: React.createElement("span", props, children),
|
||||
};
|
||||
|
||||
for (const [content, blockPattern] of [
|
||||
["```js\nconst answer = 42;\n```", "<pre"],
|
||||
["", "<img"],
|
||||
["```js\nconst answer = 42;\n```\n\nlater note", "<pre"],
|
||||
["\n\nlater note", "<img"],
|
||||
["> ```js\nconst answer = 42;\n```\n\n> later note", "<pre"],
|
||||
["> \n\n> later note", "<img"],
|
||||
]) {
|
||||
const node = renderCachedMarkdown({
|
||||
...BASE,
|
||||
components,
|
||||
content,
|
||||
leadingInlineContent: true,
|
||||
variant: "leading-fallback",
|
||||
});
|
||||
const html = renderToStaticMarkup(node);
|
||||
assert.match(html, /<p><button[^>]*>00:01<\/button><\/p>/);
|
||||
assert.ok(html.indexOf("<button") < html.indexOf(blockPattern));
|
||||
}
|
||||
});
|
||||
|
||||
test("leading inline content stays on the outer tight-list item", () => {
|
||||
const components = {
|
||||
span: ({ children, node: _node, ...props }) =>
|
||||
"data-leading-inline-content" in props
|
||||
? React.createElement(
|
||||
"button",
|
||||
{ "data-chip": "", type: "button" },
|
||||
"00:01",
|
||||
)
|
||||
: React.createElement("span", props, children),
|
||||
};
|
||||
const node = renderCachedMarkdown({
|
||||
...BASE,
|
||||
components,
|
||||
content: "- parent\n - child",
|
||||
leadingInlineContent: true,
|
||||
variant: "leading-tight-nested-list",
|
||||
});
|
||||
|
||||
assert.match(
|
||||
renderToStaticMarkup(node),
|
||||
/<li><button[^>]*>00:01<\/button>parent\s*<ul>\s*<li>child<\/li>/,
|
||||
);
|
||||
});
|
||||
|
||||
test("leading inline content participates in the cache key", () => {
|
||||
clearMarkdownNodeCache();
|
||||
const withoutLeading = renderCachedMarkdown({ ...BASE });
|
||||
const withLeading = renderCachedMarkdown({
|
||||
...BASE,
|
||||
leadingInlineContent: true,
|
||||
});
|
||||
assert.notEqual(withoutLeading, withLeading);
|
||||
});
|
||||
|
||||
test("crafted values cannot forge key-segment boundaries", () => {
|
||||
clearMarkdownNodeCache();
|
||||
// Length-prefixed segments: a single name containing arbitrary bytes must
|
||||
|
||||
Reference in New Issue
Block a user