release: 0.1.1 with Gemini 3.6 support and two blog posts (#44)
Co-authored-by: Alice <alice@prismshadow.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
49 files changed
+2249
-47
No files matched your search
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Raw-HTML rendering contract for post bodies, via react-dom/server static markup.
|
||||
*
|
||||
* Post bodies are first-party Markdown in this repo, so the blog renderer enables `rehype-raw`
|
||||
* (see the boundary note on REHYPE_PLUGINS in src/pages/blog-post.tsx). These tests pin the two
|
||||
* halves of that: the raw tags posts actually use become elements, and the surrounding Markdown
|
||||
* still parses — a `<details>` body is separated from its tags by blank lines precisely so the
|
||||
* fenced code inside it stays Markdown.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import Markdown from "react-markdown";
|
||||
import { REHYPE_PLUGINS, REMARK_PLUGINS } from "../src/pages/blog-post";
|
||||
|
||||
const render = (markdown: string) =>
|
||||
renderToStaticMarkup(
|
||||
createElement(
|
||||
Markdown,
|
||||
{ remarkPlugins: REMARK_PLUGINS, rehypePlugins: REHYPE_PLUGINS },
|
||||
markdown,
|
||||
),
|
||||
);
|
||||
|
||||
const DETAILS = `<details>
|
||||
<summary><strong>Expand: the layout</strong></summary>
|
||||
|
||||
\`\`\`text
|
||||
benchmark_config.toml
|
||||
\`\`\`
|
||||
|
||||
</details>
|
||||
`;
|
||||
|
||||
describe("blog post raw HTML", () => {
|
||||
it("renders <details>/<summary> as elements, not escaped text", () => {
|
||||
const html = render(DETAILS);
|
||||
expect(html).toContain("<details>");
|
||||
expect(html).toContain("<summary><strong>Expand: the layout</strong></summary>");
|
||||
expect(html).not.toContain("<details>");
|
||||
});
|
||||
|
||||
it("keeps the Markdown inside a <details> body as Markdown", () => {
|
||||
const html = render(DETAILS);
|
||||
expect(html).toContain("<pre>");
|
||||
expect(html).toContain("benchmark_config.toml");
|
||||
});
|
||||
|
||||
it("renders the inline <img> attributes posts use", () => {
|
||||
const html = render('<img width="491" height="481" alt="A screenshot" src="/a.png" />');
|
||||
expect(html).toContain('alt="A screenshot"');
|
||||
expect(html).toContain('width="491"');
|
||||
expect(html).not.toContain("<img");
|
||||
});
|
||||
|
||||
it("still escapes angle brackets inside code spans", () => {
|
||||
const html = render("Snapshots live at `snapshots/v<version>.tar.gz`.");
|
||||
expect(html).toContain("<code>snapshots/v<version>.tar.gz</code>");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Blog post link rendering contract, via react-dom/server static markup (node env, no DOM):
|
||||
* every link in a post body — explicit [text](url), bare autolinked URL (remark-gfm), relative
|
||||
* or #anchor — opens in a new tab (target="_blank" + rel="noreferrer"), and the optional
|
||||
* markdown link title survives the adapter's prop forwarding.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { MdLink } from "../src/pages/blog-post";
|
||||
|
||||
const render = (markdown: string) =>
|
||||
renderToStaticMarkup(
|
||||
createElement(Markdown, { remarkPlugins: [remarkGfm], components: { a: MdLink } }, markdown),
|
||||
);
|
||||
|
||||
/** All rendered <a ...> opening tags. */
|
||||
const anchors = (html: string) => html.match(/<a\b[^>]*>/g) ?? [];
|
||||
|
||||
const expectNewTab = (tag: string | undefined) => {
|
||||
expect(tag).toContain('target="_blank"');
|
||||
expect(tag).toContain('rel="noreferrer"');
|
||||
};
|
||||
|
||||
describe("blog post links", () => {
|
||||
it("external markdown links open in a new tab", () => {
|
||||
const html = render("See [the source](https://blog.google/gemini) for the numbers.");
|
||||
const tags = anchors(html);
|
||||
expect(tags).toHaveLength(1);
|
||||
expect(tags[0]).toContain('href="https://blog.google/gemini"');
|
||||
expectNewTab(tags[0]);
|
||||
expect(html).toContain(">the source</a>");
|
||||
});
|
||||
|
||||
it("internal links open in a new tab too (unconditional, per the owner's ask)", () => {
|
||||
const html = render("[practice post](/blog/natural-language-training-loop) and [top](#intro)");
|
||||
const tags = anchors(html);
|
||||
expect(tags).toHaveLength(2);
|
||||
expect(tags[0]).toContain('href="/blog/natural-language-training-loop"');
|
||||
for (const tag of tags) expectNewTab(tag);
|
||||
});
|
||||
|
||||
it("bare autolinked URLs in CJK prose open in a new tab", () => {
|
||||
const html = render("详见 https://example.com/a/very/long/path 的说明。");
|
||||
const tags = anchors(html);
|
||||
expect(tags).toHaveLength(1);
|
||||
expect(tags[0]).toContain('href="https://example.com/a/very/long/path"');
|
||||
expectNewTab(tags[0]);
|
||||
});
|
||||
|
||||
it('preserves the markdown link title from [text](url "title")', () => {
|
||||
const html = render('Read [docs](https://example.com "API docs") first.');
|
||||
const tags = anchors(html);
|
||||
expect(tags).toHaveLength(1);
|
||||
expect(tags[0]).toContain('title="API docs"');
|
||||
expectNewTab(tags[0]);
|
||||
});
|
||||
});
|
||||
@@ -96,19 +96,35 @@ describe("frontmatter mapping (author / pinned / category)", () => {
|
||||
it("reads the pinned flag and sorts the pinned post first", () => {
|
||||
for (const locale of ["en", "zh"] as const) {
|
||||
const posts = postsFor(locale);
|
||||
expect(posts.length).toBe(8);
|
||||
expect(posts.length).toBe(10);
|
||||
// The launch post stays the single pinned post; newer posts sort under it by date.
|
||||
expect(posts.filter((p) => p.pinned).map((p) => p.slug)).toEqual([
|
||||
"introducing-penguinharness",
|
||||
]);
|
||||
expect(posts[0]?.slug).toBe("introducing-penguinharness");
|
||||
expect(posts[0]?.pinned).toBe(true);
|
||||
// Pinning beats recency: the runner-up is strictly newer than the pinned post. Asserted
|
||||
// as a relation rather than a slug, because the newest date is shared by several posts
|
||||
// and the slug tie-break makes any single winner churn whenever a post is added.
|
||||
expect(posts[1]!.date > posts[0]!.date).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("filters by the practice category, newest first", () => {
|
||||
expect(postsFor("en", "practice").map((p) => p.slug)).toEqual([
|
||||
"natural-language-training-loop",
|
||||
"penguin-harness-self-improvement-with-amd-gpu",
|
||||
"local-agents-on-amd-gpus",
|
||||
]);
|
||||
});
|
||||
|
||||
it("filters by the news category, newest first", () => {
|
||||
expect(postsFor("en", "news").map((p) => p.slug)).toEqual([
|
||||
"introducing-penguinharness",
|
||||
"gemini-3-6-in-penguinharness",
|
||||
"fireworks-credits-amd",
|
||||
]);
|
||||
});
|
||||
|
||||
it("filters by the perspectives category", () => {
|
||||
expect(postsFor("en", "perspectives").map((p) => p.slug)).toEqual([
|
||||
// All three share 2026-07-22, so slug ascending is the tie-break.
|
||||
|
||||
Reference in new issue
Block a user