vendor m3e-canvas 5c83ac6, with the import rewrite scripted
upstream imports through a @/ alias, and this platform already binds @/* to src/apps/officer-web and @/components/* to its own shared components — so an unrewritten import resolves to the WRONG module rather than failing. the rewrite is mechanical and lives in scripts/vendor.sh so a re-vendor produces a readable diff. lib/ai.ts and AiPanel are vendored despite being unwanted: deleting them meant editing vendored code, and Canvas.tsx references them throughout. a faithful copy carrying an unused module beats a doctored one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { Lang, setGlobalLang } from "./i18n";
|
||||
import { buildPrompt } from "./prompt";
|
||||
import { BACK_TARGET, Doc, Item, Platform, defaultTabs, makeItem } from "./tokens";
|
||||
|
||||
const LANGS: Lang[] = ["ja", "en", "zh", "ko"];
|
||||
|
||||
/* Section headings in the order buildPrompt must emit them. */
|
||||
const SECTIONS: Record<Lang, string[]> = {
|
||||
ja: ["## カラー", "## 形・文字・動き", "## 画面構成", "## 振る舞いと画面遷移", "## 各部品のスタイル", "## 全体の指針"],
|
||||
en: ["## Colors", "## Shape, type and motion", "## Layout", "## Behavior and navigation", "## Component styles", "## General guidance"],
|
||||
zh: ["## 配色", "## 形状、字体与动效", "## 屏幕结构", "## 行为与屏幕跳转", "## 各组件的样式", "## 整体原则"],
|
||||
ko: ["## 색상", "## 모양, 글꼴 및 모션", "## 화면 구성", "## 동작 및 화면 전환", "## 부품별 스타일", "## 전체 지침"],
|
||||
};
|
||||
|
||||
const PLATFORM_LINE: Record<Lang, Record<Platform, string>> = {
|
||||
ja: { android: "実装先は Android(ネイティブアプリ)です。", web: "実装先は Web(ブラウザで動くアプリ)です。" },
|
||||
en: { android: "Build it for Android, as a native app.", web: "Build it for the web, as an app that runs in the browser." },
|
||||
zh: { android: "实现目标是 Android(原生应用)。", web: "实现目标是 Web(在浏览器中运行的应用)。" },
|
||||
ko: { android: "Android 네이티브 앱으로 구현한다.", web: "브라우저에서 실행되는 웹 앱으로 구현한다." },
|
||||
};
|
||||
|
||||
/* One phone screen with a top app bar, a connected pair of buttons (one with a
|
||||
* tap action) and a navigation bar. makeItem / defaultTabs fill in the defaults;
|
||||
* module-level language is set first so those defaults follow the test. */
|
||||
function fixture(platform: Platform = "android", extraItems: Item[] = []): Doc {
|
||||
const bar: Item = { ...makeItem("topAppBar"), id: "bar", label: "Home" };
|
||||
const save: Item = { ...makeItem("button"), id: "save", label: "Save", action: { to: BACK_TARGET, transition: "fade" } };
|
||||
const cancel: Item = { ...makeItem("button"), id: "cancel", label: "Cancel", variant: "text" };
|
||||
const nav: Item = { ...makeItem("bottomNav"), id: "nav", tabs: defaultTabs() };
|
||||
return {
|
||||
groups: [
|
||||
{ id: "g-bar", x: 16, y: 24, axis: "x", items: [bar] },
|
||||
{ id: "g-row", x: 16, y: 400, axis: "x", items: [save, cancel, ...extraItems] },
|
||||
{ id: "g-nav", x: 16, y: 812, axis: "x", items: [nav] },
|
||||
],
|
||||
frames: [{ id: "f-home", name: "Home", x: 0, y: 0 }],
|
||||
paletteKey: "purple",
|
||||
frame: "phone",
|
||||
platform,
|
||||
title: "Notes",
|
||||
brief: "",
|
||||
};
|
||||
}
|
||||
|
||||
/* Set the module-level language for the fixture helpers, then build explicitly
|
||||
* in that language — nothing is left to ambient state. */
|
||||
function build(lang: Lang, platform: Platform = "android", extraItems: Item[] = []) {
|
||||
setGlobalLang(lang);
|
||||
return buildPrompt(fixture(platform, extraItems), {}, undefined, lang);
|
||||
}
|
||||
|
||||
const lines = (prompt: string) => prompt.split("\n");
|
||||
const headings = (prompt: string) => lines(prompt).filter((l) => l.startsWith("## "));
|
||||
/* bullet lines between the style heading and the closing guidance heading */
|
||||
function styleBullets(prompt: string, lang: Lang) {
|
||||
const ls = lines(prompt);
|
||||
const style = ls.indexOf(SECTIONS[lang][4]);
|
||||
const general = ls.indexOf(SECTIONS[lang][5]);
|
||||
return ls.slice(style + 1, general).filter((l) => l.startsWith("- "));
|
||||
}
|
||||
|
||||
const QUOTED: Record<Lang, { label: string; others: string[] }> = {
|
||||
ja: { label: "「Save」", others: ['"Save"', "“Save”"] },
|
||||
en: { label: '"Save"', others: ["「Save」", "“Save”"] },
|
||||
zh: { label: "“Save”", others: ["「Save」", '"Save"'] },
|
||||
ko: { label: '"Save"', others: ["「Save」", "“Save”"] },
|
||||
};
|
||||
|
||||
describe("buildPrompt structure", () => {
|
||||
afterEach(() => setGlobalLang("ja")); // restore the module default
|
||||
|
||||
it.each(LANGS)("orders its sections the same way in %s", (lang) => {
|
||||
expect(headings(build(lang))).toEqual(SECTIONS[lang]);
|
||||
});
|
||||
|
||||
it.each(LANGS)("names the requested platform on the intro lines in %s", (lang) => {
|
||||
const android = lines(build(lang, "android"));
|
||||
const web = lines(build(lang, "web"));
|
||||
expect(android[2]).toBe(PLATFORM_LINE[lang].android);
|
||||
expect(web[2]).toBe(PLATFORM_LINE[lang].web);
|
||||
});
|
||||
|
||||
it("names Android when the doc picks no platform", () => {
|
||||
setGlobalLang("en");
|
||||
const { platform, ...doc } = fixture();
|
||||
expect(lines(buildPrompt(doc, {}, undefined, "en"))[2]).toBe(PLATFORM_LINE.en.android);
|
||||
});
|
||||
|
||||
it.each(LANGS)("writes one style note per part kind in use in %s", (lang) => {
|
||||
expect(styleBullets(build(lang), lang)).toHaveLength(3); // topAppBar, button, bottomNav
|
||||
const chip: Item = { ...makeItem("chip"), id: "chip" };
|
||||
expect(styleBullets(build(lang, "android", [chip]), lang)).toHaveLength(4);
|
||||
});
|
||||
|
||||
it.each(LANGS)("quotes labels with %s punctuation", (lang) => {
|
||||
const prompt = build(lang);
|
||||
expect(prompt).toContain(QUOTED[lang].label);
|
||||
for (const other of QUOTED[lang].others) expect(prompt).not.toContain(other);
|
||||
});
|
||||
|
||||
it("follows its lang argument regardless of ambient module state", () => {
|
||||
setGlobalLang("en");
|
||||
const doc = fixture();
|
||||
setGlobalLang("zh");
|
||||
const prompt = buildPrompt(doc, {}, undefined, "ja");
|
||||
expect(headings(prompt)).toEqual(SECTIONS.ja);
|
||||
expect(prompt).toContain("「Save」");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user