✨ feat(prototype): 代码阅读枚举、Celery 卡片原型,侧栏改成共用组件
This commit is contained in:
1 parent
350d6f020a
commit
905d7845b5
3 files changed
+164
-10
No files matched your search
@@ -24,6 +24,8 @@
|
||||
| `Prototype / routes.py · 编辑 / 预览` | luffy-agent-platform 的 `backend/src/luffy_agent/user/routes.py` | Python:带装饰器、默认值、返回类型的 FastAPI 路由函数,属性表;`is not None` → 不是 空 |
|
||||
| `Prototype / envLock.ts · 预览(编辑区变窄)` | — | 窗口只有 1000 宽时块跟着变窄,长句换行 |
|
||||
| `Prototype / 库规则 · SQLAlchemy 数据表` | luffy 的 `backend/src/luffy_agent/referral/models.py` 里的 `ReferralRelation` | 库规则(文件导入了 sqlalchemy 才启用)把模型类整块换成「数据表」卡片:名称 / 表名 / 说明 / 继承,字段表(字段 / 类型 / 约束 / 说明)和索引表;`__tablename__`、`__table_args__`、各字段块并进卡片 |
|
||||
| `Prototype / 枚举` | luffy 的 `referral/models.py` 里的 `CommissionStatus` | 枚举卡片:名称 / 访问级别 / 继承,成员收成一张表(成员 / 值 / 说明),说明取行尾注释;Python 用库规则(导入了 enum 才启用),TypeScript 的 enum 由语言插件报结构,画成同一张卡片 |
|
||||
| `Prototype / 库规则 · Celery` | luffy 的 `backend/src/luffy_agent/celery_app.py` | 库规则(导入了 celery 才启用)两张卡片:「Celery 应用」名称 / 应用名 / 消息队列 / 结果存储;「定时任务」应用 + 任务表(任务 / 频率 / 说明),频率换算成「每 5 分钟」「每天」,说明取行尾注释,`**` 展开的另起一行 |
|
||||
| `Prototype / 遍历 · 推导式` | luffy 邀请码工具的 `"".join(secrets.choice(ALPHABET) for _ in range(length))`,外加带条件的列表、字典推导 | 写在表达式里的遍历原地套一个带循环图标和结果类型(生成器 / 列表 / 字典)的小框,框里按读的顺序重排:「遍历 range(length) 的每个 _,仅当 …,取 …」 |
|
||||
| `Prototype / 筛选 · 默认` | `envLock.ts` | 预览顶部固定一条筛选栏:「筛选」+ 当前文件出现过的属性各一个下拉框(种类、访问级别、异步、仅类型)+ 右边「共 33 块」 |
|
||||
| `Prototype / 筛选 · 按访问级别` | 同上 | 访问级别下拉菜单打开,只勾「公共」:两个私有常量和私有函数 `chmodAll`(连同函数体)藏起来;条件、返回这些没有访问级别的块照常显示;右边「已隐藏 3 块 · 清除」 |
|
||||
@@ -56,7 +58,10 @@
|
||||
- `Reader / ModeToggle`:编辑器右上角的「编辑 / 预览」。
|
||||
- `Explorer / TreeRow`:侧栏文件树的一行,文件夹展开 / 收起 / 展开且悬停,文件默认 / 选中 / 有改动 / 有改动且选中 / 悬停,共 8 个。名字后面的注释层默认藏起来,有注释的实例里打开;悬停状态行尾带「添加注释」「编辑注释」两个按钮,实例里按有没有注释显示其中一个。
|
||||
- `Explorer / Header`:侧栏顶部。
|
||||
- `Explorer / Sidebar`:`File=<文件>`,选中某个示例文件时的整个侧栏,各窗口共用;文件注释那几张要改行的状态,单独画。
|
||||
- `Reader / TableCard`:SQLAlchemy 规则认出的数据表卡片。
|
||||
- `Reader / EnumCard`:枚举卡片。
|
||||
- `Reader / CeleryAppCard`、`Reader / BeatCard`:Celery 规则的两张卡片。
|
||||
- `Reader / IterationBlock`:`Sample=generator | list | dict`,三种推导式的返回块。
|
||||
- `Reader / FilterBar`:`Active=none | access | kind`,预览顶部的筛选栏,打开的下拉菜单是勾选列表,每个值后面是块数。
|
||||
- `Window / QuickInput`:VS Code 顶部弹出的单行输入框,标题和内容在实例里改。
|
||||
|
||||
+130
-8
@@ -951,6 +951,88 @@ function tableCard(ctx) {
|
||||
return node;
|
||||
}
|
||||
|
||||
// ─── 枚举 ────────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// Python 的枚举来自自带的 enum 库,用库规则认(文件导入了 enum 才启用);TypeScript 的 enum 是语言自己的写法,由语言插件报结构。
|
||||
// 两边画成同一张卡片:名称 / 访问级别 / 继承,成员收成一张表(成员 / 值 / 说明),说明取行尾注释或成员下面的三引号说明。
|
||||
// 示例:luffy 的 backend/src/luffy_agent/referral/models.py 里的 CommissionStatus。
|
||||
const ENUM_SAMPLE = {
|
||||
name: "CommissionStatus", access: "公共", bases: "str, enum.Enum",
|
||||
members: [["PENDING", '"pending"', "待结算"], ["AVAILABLE", '"available"', "可提现"], ["WITHDRAWING", '"withdrawing"', "提现中"],
|
||||
["WITHDRAWN", '"withdrawn"', "已提现"], ["INVALID", '"invalid"', "已失效"]],
|
||||
};
|
||||
const ENUM_KIND = { label: "枚举", color: "#b180d7" };
|
||||
|
||||
/** Reader / EnumCard — State=preview:枚举卡片。 */
|
||||
function enumCard(ctx) {
|
||||
const sample = ENUM_SAMPLE;
|
||||
const { node, content } = cardShell(ctx, "State=preview", ENUM_KIND, { kids: false });
|
||||
propRow(ctx, content, "名称", [{ text: sample.name, color: COLORS.tokenType }]);
|
||||
propRow(ctx, content, "访问级别", [{ text: sample.access, color: COLORS.tokenControl }]);
|
||||
propRow(ctx, content, "继承", sample.bases.split(", ").flatMap((base, index) =>
|
||||
[...(index ? [{ text: ", " }] : []), { text: base, color: COLORS.tokenType }]));
|
||||
propRow(ctx, content, "成员", countSegments(sample.members), { twisty: true });
|
||||
propTable(ctx, content, "Members", [["成员", 200], ["值", 200], ["说明", 0]], sample.members.map(([name, value, note]) => [`Member / ${name}`, [
|
||||
[{ text: name, color: COLORS.tokenConstant }],
|
||||
[{ text: value, color: COLORS.tokenString }],
|
||||
[{ text: note, color: COLORS.comment }],
|
||||
]]));
|
||||
return node;
|
||||
}
|
||||
|
||||
// ─── 库规则:Celery ──────────────────────────────────────────────────────────
|
||||
//
|
||||
// 文件导入了 celery 才启用。两张卡片:创建应用(X = Celery(...))和定时任务表(X.conf.beat_schedule = {...});
|
||||
// 改配置、自动发现任务这些语句照旧。示例:luffy 的 backend/src/luffy_agent/celery_app.py。
|
||||
const CELERY_APP = { name: "celery_app", app: '"luffy_agent"', broker: "settings.CELERY_BROKER_URL", backend: "settings.CELERY_RESULT_BACKEND" };
|
||||
const CELERY_APP_KIND = { label: "Celery 应用", color: "#98c379", badgeWidth: 76 };
|
||||
const BEAT_KIND = { label: "定时任务", color: "#56b6c2", badgeWidth: 60 };
|
||||
// 频率按调度表达式换算成人话:秒数算式 → 每 N 秒 / 分钟 / 小时 / 天,crontab → 每天 hh:mm 这类;换算不了的照原样。
|
||||
const BEAT_SCHEDULE = {
|
||||
app: "celery_app",
|
||||
entries: [
|
||||
["commerce.cancel_timeout_pending_orders", "每 5 分钟", ""],
|
||||
["commerce.retry_failed_fulfillments", "每 10 分钟", ""],
|
||||
["commerce.check_pending_refunds", "每 30 分钟", ""],
|
||||
["commerce.check_channel_pending_manual_slices", "每 10 分钟", "手动退款切片对账: 微信回调丢失兜底"],
|
||||
["deployment.reconcile_inflight", "每 5 分钟", "发布管线 in-flight 行卡死兜底: 按阶段重新派发"],
|
||||
["deployment.reclaim_expired_slot_deployments", "每 10 分钟", "到期部署位对账: 换票或拆除 (#159)"],
|
||||
["deployment.notify_expiring_deployments", "每天", "每天一轮到期提醒扫描 (#274)"],
|
||||
["membership.grant_monthly_entitlements", "每小时", ""],
|
||||
["storage.poll_app_data_usage", "每 get_settings().STORAGE_USAGE_POLL_INTERVAL_SEC 秒", ""],
|
||||
],
|
||||
spread: "_ops_beat_entries(settings.OPS_DASHBOARD_ENABLED)",
|
||||
};
|
||||
|
||||
/** Reader / CeleryAppCard — State=preview:创建 Celery 应用。 */
|
||||
function celeryAppCard(ctx) {
|
||||
const app = CELERY_APP;
|
||||
const { node, content } = cardShell(ctx, "State=preview", CELERY_APP_KIND, { kids: false });
|
||||
propRow(ctx, content, "名称", [{ text: app.name, color: COLORS.tokenVariable }]);
|
||||
propRow(ctx, content, "应用名", [{ text: app.app, color: COLORS.tokenString }]);
|
||||
propRow(ctx, content, "消息队列", pySegments(app.broker));
|
||||
propRow(ctx, content, "结果存储", pySegments(app.backend));
|
||||
return node;
|
||||
}
|
||||
|
||||
/** Reader / BeatCard — State=preview:定时任务表,每条一行:任务 / 频率 / 说明(行尾注释);** 展开的另起一行。 */
|
||||
function beatCard(ctx) {
|
||||
const beat = BEAT_SCHEDULE;
|
||||
const { node, content } = cardShell(ctx, "State=preview", BEAT_KIND, { kids: false });
|
||||
propRow(ctx, content, "应用", [{ text: beat.app, color: COLORS.tokenVariable }]);
|
||||
propRow(ctx, content, "任务", [{ text: `${beat.entries.length} 条,另有展开 1 处`, color: COLORS.desc }], { twisty: true });
|
||||
propTable(ctx, content, "Entries", [["任务", 380], ["频率", 170], ["说明", 0]], [
|
||||
...beat.entries.map(([task, every, note]) => [`Entry / ${task}`, [
|
||||
[{ text: task, color: COLORS.tokenString }],
|
||||
every.includes("get_settings") ? [{ text: "每 ", color: COLORS.tokenControl }, ...pySegments("get_settings().STORAGE_USAGE_POLL_INTERVAL_SEC"), { text: " 秒", color: COLORS.tokenControl }]
|
||||
: [{ text: every, color: COLORS.tokenControl }],
|
||||
note ? [{ text: note, color: COLORS.comment }] : [],
|
||||
]]),
|
||||
["Entry / 展开", [[{ text: "展开", color: COLORS.desc }], pySegments(beat.spread), []]],
|
||||
]);
|
||||
return node;
|
||||
}
|
||||
|
||||
// ─── 筛选 ────────────────────────────────────────────────────────────────────
|
||||
//
|
||||
// 每块带几条「属性 = 值」,由语言插件从语法树换算成各语言统一的属性,翻译插件给属性和值起显示名;
|
||||
@@ -1289,8 +1371,8 @@ function cardShell(ctx, name, spec, o = {}) {
|
||||
node.paddingRight = 10;
|
||||
const fold = append(node, frame(ctx, "Fold", { dir: "HORIZONTAL", width: 14, height: 20, align: "CENTER", justify: "CENTER" }));
|
||||
if (o.kids) append(fold, icon(ctx, "chevron-down", COLORS.desc, 14, { name: "Fold icon" }));
|
||||
const badge = append(node, frame(ctx, "Badge", { dir: "HORIZONTAL", width: 44, height: 20, radius: 4, align: "CENTER", justify: "CENTER",
|
||||
fill: spec.color, fillOpacity: 0.18 }));
|
||||
const badge = append(node, frame(ctx, "Badge", { dir: "HORIZONTAL", width: spec.badgeWidth || 44, height: 20, radius: 4, align: "CENTER",
|
||||
justify: "CENTER", fill: spec.color, fillOpacity: 0.18 }));
|
||||
text(ctx, badge, "Kind", spec.label, { hug: true, medium: true, size: 11, lineHeight: 14, color: spec.color });
|
||||
const content = append(node, frame(ctx, "Content", { width: 10, gap: 3 }), true);
|
||||
const toggle = append(node, frame(ctx, "Toggle", { dir: "HORIZONTAL", px: 8, py: 2, gap: 4, radius: 4, align: "CENTER",
|
||||
@@ -1538,6 +1620,9 @@ function createComponents(ctx, page) {
|
||||
lib.block = make("Reader / CodeBlock", BLOCK_VARIANTS.map(([kind, state]) => `${kind}/${state}`), 2,
|
||||
(entry) => codeBlock(ctx, ...entry.split("/")), (item) => item.node);
|
||||
lib.tableCard = make("Reader / TableCard", ["preview"], 1, () => tableCard(ctx));
|
||||
lib.enumCard = make("Reader / EnumCard", ["preview"], 1, () => enumCard(ctx));
|
||||
lib.celeryApp = make("Reader / CeleryAppCard", ["preview"], 1, () => celeryAppCard(ctx));
|
||||
lib.beat = make("Reader / BeatCard", ["preview"], 1, () => beatCard(ctx));
|
||||
lib.iteration = make("Reader / IterationBlock", ITERATION_SAMPLES.map((sample) => sample.key), 3,
|
||||
(key) => iterationBlock(ctx, ITERATION_SAMPLES.find((sample) => sample.key === key)));
|
||||
lib.filterBar = make("Reader / FilterBar", ["none", "access", "kind"], 3, (active) => filterBar(ctx, active));
|
||||
@@ -1545,6 +1630,7 @@ function createComponents(ctx, page) {
|
||||
(entry) => treeRow(ctx, ...entry.split("/")));
|
||||
lib.quickInput = make("Window / QuickInput", ["typing"], 1, () => quickInput(ctx));
|
||||
lib.explorerHeader = make("Explorer / Header", ["default"], 1, () => explorerHeader(ctx));
|
||||
lib.sidebar = make("Explorer / Sidebar", fileKeys, 3, (fileKey) => sidebarComponent(ctx, lib, fileKey));
|
||||
lib.windowTitle = make("Window / TitleBar", ["active"], 1, () => windowTitle(ctx));
|
||||
lib.activity = make("Window / ActivityBar", ["reader"], 1, () => activityBar(ctx));
|
||||
lib.status = make("Window / StatusBar", MODES, 1, (mode) => statusBar(ctx, mode));
|
||||
@@ -1579,11 +1665,35 @@ function rowVariant(item, selected, hover) {
|
||||
return isSelected ? "file/selected" : "file/default";
|
||||
}
|
||||
|
||||
/** 侧栏:「代码阅读」视图,外观照搬 VS Code 资源管理器。o.hover:鼠标停在哪一行;o.notes:各行的注释。 */
|
||||
/**
|
||||
* 侧栏:「代码阅读」视图,外观照搬 VS Code 资源管理器。
|
||||
* 普通窗口放 Explorer / Sidebar 的实例(每个示例文件一个变体,窗口之间共用);
|
||||
* 文件注释那几张要改行的悬停状态和注释,单独画。o.hover:鼠标停在哪一行;o.notes:各行的注释。
|
||||
*/
|
||||
function sidebar(ctx, parent, lib, selected, o = {}) {
|
||||
const workspace = WORKSPACES[FILES[selected].workspace];
|
||||
if (!o.hover && !o.notes) {
|
||||
const instance = lib.sidebar.get(selected).createInstance();
|
||||
instance.name = "Sidebar";
|
||||
append(parent, instance);
|
||||
instance.layoutSizingVertical = "FILL";
|
||||
const rows = new Map(WORKSPACES[FILES[selected].workspace].rows.filter((item) => item.file)
|
||||
.map((item) => [item.file, instance.findOne((node) => node.name === `Row / ${item.name}`)]));
|
||||
return { pane: instance, rows };
|
||||
}
|
||||
const pane = append(parent, frame(ctx, "Sidebar", { width: SIDEBAR_W, fill: COLORS.side }));
|
||||
pane.layoutSizingVertical = "FILL";
|
||||
return { pane, rows: sidebarContent(ctx, pane, lib, selected, o) };
|
||||
}
|
||||
|
||||
/** Explorer / Sidebar — File=<文件>:选中这个文件时的整个侧栏。 */
|
||||
function sidebarComponent(ctx, lib, selected) {
|
||||
const node = frame(ctx, `File=${selected}`, { component: true, width: SIDEBAR_W, height: MAIN_H, fill: COLORS.side });
|
||||
sidebarContent(ctx, node, lib, selected, {});
|
||||
return node;
|
||||
}
|
||||
|
||||
function sidebarContent(ctx, pane, lib, selected, o) {
|
||||
const workspace = WORKSPACES[FILES[selected].workspace];
|
||||
pane.clipsContent = true;
|
||||
pane.overflowDirection = "VERTICAL";
|
||||
const header = lib.explorerHeader.get("default").createInstance();
|
||||
@@ -1619,7 +1729,7 @@ function sidebar(ctx, parent, lib, selected, o = {}) {
|
||||
}
|
||||
if (item.file) rows.set(item.file, instance);
|
||||
}
|
||||
return { pane, rows };
|
||||
return rows;
|
||||
}
|
||||
|
||||
/** 编辑模式:VS Code 原生编辑器,整份源码。 */
|
||||
@@ -1763,6 +1873,18 @@ async function build(api, stage = () => {}) {
|
||||
const tableInstance = lib.tableCard.get("preview").createInstance();
|
||||
tableInstance.name = `Block / ${SQLA_MODEL.name}`;
|
||||
append(tableBoard, tableInstance, true);
|
||||
const enumBoard = frame(ctx, "Prototype / 枚举", { width: TREE_W + 2 * VIEW_PAD, pad: VIEW_PAD, gap: 8, fill: COLORS.editor });
|
||||
page.appendChild(enumBoard);
|
||||
const enumInstance = lib.enumCard.get("preview").createInstance();
|
||||
enumInstance.name = `Block / ${ENUM_SAMPLE.name}`;
|
||||
append(enumBoard, enumInstance, true);
|
||||
const celeryBoard = frame(ctx, "Prototype / 库规则 · Celery", { width: TREE_W + 2 * VIEW_PAD, pad: VIEW_PAD, gap: 8, fill: COLORS.editor });
|
||||
page.appendChild(celeryBoard);
|
||||
for (const [key, name] of [["celeryApp", "Block / celery_app"], ["beat", "Block / beat_schedule"]]) {
|
||||
const instance = lib[key].get("preview").createInstance();
|
||||
instance.name = name;
|
||||
append(celeryBoard, instance, true);
|
||||
}
|
||||
const iteration = frame(ctx, "Prototype / 遍历 · 推导式", { width: TREE_W + 2 * VIEW_PAD, pad: VIEW_PAD, gap: 8, fill: COLORS.editor });
|
||||
page.appendChild(iteration);
|
||||
for (const sample of ITERATION_SAMPLES) {
|
||||
@@ -1816,7 +1938,7 @@ async function build(api, stage = () => {}) {
|
||||
|
||||
stage("排布画板");
|
||||
const screens = [...Object.values(boards).flatMap((pair) => [pair.edit.screen, pair.preview.screen]), narrow.screen,
|
||||
...Object.values(notes).map((board) => board.screen), ...Object.values(filters).map((board) => board.screen), iteration, tableBoard];
|
||||
...Object.values(notes).map((board) => board.screen), ...Object.values(filters).map((board) => board.screen), iteration, tableBoard, enumBoard, celeryBoard];
|
||||
let y = 0;
|
||||
for (const screen of screens) {
|
||||
screen.x = 0;
|
||||
@@ -1834,7 +1956,7 @@ async function build(api, stage = () => {}) {
|
||||
{ nodeId: filters.none.screen.id, name: "筛选" }];
|
||||
page.selection = [start];
|
||||
api.viewport.scrollAndZoomIntoView(page.selection);
|
||||
return { page, components, boards, narrow, notes, filters, iteration, tableBoard, screens };
|
||||
return { page, components, boards, narrow, notes, filters, iteration, tableBoard, enumBoard, celeryBoard, screens };
|
||||
}
|
||||
|
||||
async function run(api) {
|
||||
@@ -1853,6 +1975,6 @@ async function run(api) {
|
||||
}
|
||||
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = { SQLA_MODEL, ITERATION_SAMPLES, iterationSegments, facetCounts, filteredOut, filterPresets, blockAttributes, FILTER_FILE, NOTES, NOTE_DRAFT, PLAIN_KINDS, parseImport, parseVariable, translateCode, build, run, nextPageName, PAGE_NAME, NAMESPACE, TREE, WORKSPACES, FILES, CODE_TREE, BLOCK_KINDS, SOURCES, blockCode, lineRange, walkTree, translateBlock, highlightCode, parseSignature, typeSegments };
|
||||
module.exports = { BEAT_SCHEDULE, ENUM_SAMPLE, SQLA_MODEL, ITERATION_SAMPLES, iterationSegments, facetCounts, filteredOut, filterPresets, blockAttributes, FILTER_FILE, NOTES, NOTE_DRAFT, PLAIN_KINDS, parseImport, parseVariable, translateCode, build, run, nextPageName, PAGE_NAME, NAMESPACE, TREE, WORKSPACES, FILES, CODE_TREE, BLOCK_KINDS, SOURCES, blockCode, lineRange, walkTree, translateBlock, highlightCode, parseSignature, typeSegments };
|
||||
}
|
||||
if (typeof figma !== "undefined") void run(figma);
|
||||
+29
-2
@@ -2,7 +2,7 @@ const assert = require("node:assert/strict");
|
||||
const { test } = require("node:test");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { SQLA_MODEL, ITERATION_SAMPLES, iterationSegments, facetCounts, filteredOut, filterPresets, FILTER_FILE, NOTES, NOTE_DRAFT, PLAIN_KINDS, build, run, nextPageName, PAGE_NAME, NAMESPACE, WORKSPACES, FILES, CODE_TREE, BLOCK_KINDS, SOURCES, blockCode, lineRange, walkTree, translateBlock, highlightCode, parseSignature, parseImport, parseVariable, typeSegments } = require("./code.js");
|
||||
const { BEAT_SCHEDULE, ENUM_SAMPLE, SQLA_MODEL, ITERATION_SAMPLES, iterationSegments, facetCounts, filteredOut, filterPresets, FILTER_FILE, NOTES, NOTE_DRAFT, PLAIN_KINDS, build, run, nextPageName, PAGE_NAME, NAMESPACE, WORKSPACES, FILES, CODE_TREE, BLOCK_KINDS, SOURCES, blockCode, lineRange, walkTree, translateBlock, highlightCode, parseSignature, parseImport, parseVariable, typeSegments } = require("./code.js");
|
||||
const { fakeFigma } = require("../../figma-lab/fake-figma");
|
||||
|
||||
function action(node) { return node.reactions[0].actions[0]; }
|
||||
@@ -28,11 +28,15 @@ const EXPECTED_SETS = [
|
||||
["Reader / ModeToggle", 2],
|
||||
["Reader / CodeBlock", 18],
|
||||
["Reader / TableCard", 1],
|
||||
["Reader / EnumCard", 1],
|
||||
["Reader / CeleryAppCard", 1],
|
||||
["Reader / BeatCard", 1],
|
||||
["Reader / IterationBlock", 3],
|
||||
["Reader / FilterBar", 3],
|
||||
["Explorer / TreeRow", 8],
|
||||
["Window / QuickInput", 1],
|
||||
["Explorer / Header", 1],
|
||||
["Explorer / Sidebar", 3],
|
||||
["Window / TitleBar", 1],
|
||||
["Window / ActivityBar", 1],
|
||||
["Window / StatusBar", 2],
|
||||
@@ -394,6 +398,29 @@ test("the SQLAlchemy rule turns a model class into a table card", async () => {
|
||||
assert.equal(named(named(inviter, "约束"), "Text").characters, "索引 · 外键 sys_user.id(删除时禁止)");
|
||||
});
|
||||
|
||||
test("enums get their own card with a member table", async () => {
|
||||
const api = fakeFigma();
|
||||
const { enumBoard } = await build(api);
|
||||
const card = enumBoard.findOne((node) => node.name === `Block / ${ENUM_SAMPLE.name}`);
|
||||
assert.equal(named(card, "Kind").characters, "枚举");
|
||||
assert.equal(card.findAll((node) => node.name.startsWith("Member / ")).length, ENUM_SAMPLE.members.length);
|
||||
assert.equal(named(named(card.findOne((node) => node.name === "Member / PENDING"), "说明"), "Text").characters, "待结算");
|
||||
});
|
||||
|
||||
test("the Celery rule draws the app and the beat schedule", async () => {
|
||||
const api = fakeFigma();
|
||||
const { celeryBoard } = await build(api);
|
||||
const app = celeryBoard.findOne((node) => node.name === "Block / celery_app");
|
||||
assert.equal(named(app, "Kind").characters, "Celery 应用");
|
||||
assert.deepEqual(app.findAll((node) => node.name.startsWith("Prop / ")).map((node) => node.name),
|
||||
["Prop / 名称", "Prop / 应用名", "Prop / 消息队列", "Prop / 结果存储"]);
|
||||
const beat = celeryBoard.findOne((node) => node.name === "Block / beat_schedule");
|
||||
assert.equal(named(beat, "Kind").characters, "定时任务");
|
||||
assert.equal(beat.findAll((node) => node.name.startsWith("Entry / ")).length, BEAT_SCHEDULE.entries.length + 1);
|
||||
const entry = beat.findOne((node) => node.name === "Entry / deployment.notify_expiring_deployments");
|
||||
assert.equal(named(named(entry, "频率"), "Text").characters, "每天");
|
||||
});
|
||||
|
||||
test("boards and sets sit on the page without overlapping", async () => {
|
||||
const api = fakeFigma();
|
||||
const result = await build(api);
|
||||
@@ -416,7 +443,7 @@ test("the plugin stays small enough for Figma to build quickly", async () => {
|
||||
if (node.type !== "INSTANCE") node.children.forEach(walk);
|
||||
};
|
||||
result.page.children.forEach(walk);
|
||||
// 卡死那次是 5000 多个图层,而且每次读宽度都逼 Figma 重排;现在 16 张画板、36 张属性表约 2600 个
|
||||
// 卡死那次是 5000 多个图层,而且每次读宽度都逼 Figma 重排;侧栏做成共用组件后,现在 20 张画板约 2800 个
|
||||
assert.ok(owned < 3000, `page owns ${owned} layers outside instances`);
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user