补齐 AGC「生成动画」面板的宿主样式并加样式缺口守卫
- resourceCanvasChrome.css 逐条照抄 src/index.css 的角色动画面板规则:面板 / 提示词 textarea / 动作预设 / 底部动作行 / 提交按钮,以及关闭键、素材缩略图与 reference-chip 全族 - 窄屏(≤720px)与网页端同口径:面板落到底部整宽、动作竖排、提交按钮整宽 - 新增 tests/resourceCanvasCharacterAnimationPanelStyle.test.tsx:渲染共享动画面板,按 src/main.tsx 的真实 import 关系解析 AGC 实际加载的样式表,断言「网页端给出规则的类 AGC 必须也有一条」并抽查撑开布局的关键声明 - 变异验证:临时移除本次补的样式块,守卫用例列出缺规则的 15 个类并红灯
This commit is contained in:
@@ -1408,3 +1408,339 @@
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/*
|
||||
* 角色动画生成面板(共享 `ImageCanvasCharacterAnimationPanelView`)与它的素材缩略图。
|
||||
*
|
||||
* 同一处样式缺口:`image-canvas-editor__character-animation-*`、`__generation-close` 与
|
||||
* `__reference-strip/__reference-slot/__reference-chip*` 在网页端由整站表 `src/index.css` 提供,
|
||||
* 而 AGC 不引那张表、宿主 chrome 表又只照抄了 composer 的通用规则。结果是这个面板只剩通用
|
||||
* composer 外观:素材缩略图、预设胶囊、提交按钮与右上角关闭键全部落回默认流布局,面板里出现
|
||||
* 一大块空白、预设竖排、`生成80泥点` 悬在中间(客户端验收现场截图)。
|
||||
*
|
||||
* 下面逐条照抄 `src/index.css` 的同名规则(注释里给行号),不手写近似值;AGC 不会渲染的兄弟
|
||||
* 选择器不搬(例如 `.image-canvas-editor__character-animation-head` 在这版组件里没有节点)。
|
||||
*/
|
||||
|
||||
/* src/index.css:6776 */
|
||||
.image-canvas-editor__generation-composer--character-animation {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
grid-template-rows: auto auto auto auto auto;
|
||||
width: min(42rem, calc(100% - 1.5rem));
|
||||
}
|
||||
|
||||
/* src/index.css:6874 */
|
||||
.image-canvas-editor__generation-close {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #374151;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
/* src/index.css:6906 */
|
||||
.image-canvas-editor__reference-strip {
|
||||
display: flex;
|
||||
grid-column: 1 / -1;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 0.36rem;
|
||||
overflow-x: auto;
|
||||
padding: 0 2.2rem 0.02rem 0;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
/* src/index.css:6917 */
|
||||
.image-canvas-editor__reference-slot {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
width: 3.95rem;
|
||||
height: 3.95rem;
|
||||
}
|
||||
|
||||
/* src/index.css:6925 */
|
||||
.image-canvas-editor__reference-chip {
|
||||
position: relative;
|
||||
display: inline-grid;
|
||||
width: 3.95rem;
|
||||
min-width: 3.95rem;
|
||||
max-width: 3.95rem;
|
||||
height: 3.95rem;
|
||||
min-height: 3.95rem;
|
||||
grid-template-rows: 1.42rem auto;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
gap: 0.28rem;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(148, 163, 184, 0.16);
|
||||
border-radius: 1rem;
|
||||
background: #f2f3f5;
|
||||
padding: 0.62rem 0.36rem 0.5rem;
|
||||
color: #a3a8ae;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 850;
|
||||
line-height: 1.04;
|
||||
text-align: center;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* src/index.css:6950 */
|
||||
button.image-canvas-editor__reference-chip {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.image-canvas-editor__reference-chip:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
border-color: rgba(148, 163, 184, 0.22);
|
||||
background: #eef0f2;
|
||||
}
|
||||
|
||||
button.image-canvas-editor__reference-chip:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.62;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* src/index.css:6966 */
|
||||
.image-canvas-editor__reference-chip-icon {
|
||||
display: grid;
|
||||
width: 1.42rem;
|
||||
height: 1.42rem;
|
||||
flex: 0 0 auto;
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
border-radius: 0.36rem;
|
||||
background: transparent;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.image-canvas-editor__reference-chip-icon img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0.36rem;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* src/index.css:6985 */
|
||||
.image-canvas-editor__reference-chip-label {
|
||||
display: -webkit-box;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
/* src/index.css:6996 */
|
||||
.image-canvas-editor__reference-chip-source-label {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
clip-path: inset(50%);
|
||||
}
|
||||
|
||||
/* src/index.css:7006 */
|
||||
.image-canvas-editor__reference-chip-remove {
|
||||
position: absolute;
|
||||
top: 0.16rem;
|
||||
right: 0.16rem;
|
||||
z-index: 2;
|
||||
display: grid;
|
||||
width: 1.12rem;
|
||||
height: 1.12rem;
|
||||
place-items: center;
|
||||
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||
border-radius: 999px;
|
||||
background: rgba(15, 23, 42, 0.86);
|
||||
color: #ffffff;
|
||||
opacity: 0;
|
||||
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.18);
|
||||
pointer-events: none;
|
||||
transform: scale(0.88);
|
||||
transition:
|
||||
opacity 120ms ease,
|
||||
transform 120ms ease;
|
||||
}
|
||||
|
||||
.image-canvas-editor__reference-chip-remove--always-visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.image-canvas-editor__reference-slot:hover
|
||||
.image-canvas-editor__reference-chip-remove,
|
||||
.image-canvas-editor__reference-slot:focus-within
|
||||
.image-canvas-editor__reference-chip-remove {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
@media (hover: none) {
|
||||
.image-canvas-editor__reference-chip-remove {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.image-canvas-editor__reference-chip-remove:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.image-canvas-editor__reference-chip-remove:hover:not(:disabled) {
|
||||
background: rgba(220, 38, 38, 0.95);
|
||||
}
|
||||
|
||||
/* src/index.css:7061:带图缩略图的图标尺寸(角色动画的源图就是这一档) */
|
||||
.image-canvas-editor__reference-chip--has-image
|
||||
.image-canvas-editor__reference-chip-icon {
|
||||
width: 1.74rem;
|
||||
height: 1.74rem;
|
||||
border-radius: 0.48rem;
|
||||
}
|
||||
|
||||
.image-canvas-editor__reference-chip--has-image
|
||||
.image-canvas-editor__reference-chip-icon
|
||||
img {
|
||||
border-radius: 0.48rem;
|
||||
}
|
||||
|
||||
/* src/index.css:7074–7112:tone 取色(AGC 目前渲染 quick-edit 与 character,其余一并有备) */
|
||||
.image-canvas-editor__reference-chip--spec
|
||||
.image-canvas-editor__reference-chip-icon {
|
||||
color: #8b5cf6;
|
||||
}
|
||||
|
||||
.image-canvas-editor__reference-chip--icon
|
||||
.image-canvas-editor__reference-chip-icon {
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.image-canvas-editor__reference-chip--ui
|
||||
.image-canvas-editor__reference-chip-icon {
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.image-canvas-editor__reference-chip--character
|
||||
.image-canvas-editor__reference-chip-icon {
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.image-canvas-editor__reference-chip--quick-edit {
|
||||
background: #f3f4f6;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.image-canvas-editor__reference-chip--quick-edit
|
||||
.image-canvas-editor__reference-chip-icon {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.image-canvas-editor__reference-chip--video
|
||||
.image-canvas-editor__reference-chip-icon {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.image-canvas-editor__reference-chip--audio
|
||||
.image-canvas-editor__reference-chip-icon {
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
/* src/index.css:8728 */
|
||||
.image-canvas-editor__character-animation-panel {
|
||||
position: absolute;
|
||||
z-index: 14;
|
||||
display: grid;
|
||||
width: min(42rem, calc(100vw - 1.5rem));
|
||||
max-height: min(34rem, calc(100% - 1.5rem));
|
||||
gap: 0.58rem;
|
||||
overflow: auto;
|
||||
border: 1px solid rgba(148, 163, 184, 0.36);
|
||||
border-radius: 1.1rem;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
padding: 0.76rem;
|
||||
font-size: 0.82rem;
|
||||
box-shadow: 0 22px 48px rgba(15, 23, 42, 0.22);
|
||||
}
|
||||
|
||||
/* src/index.css:8753 */
|
||||
.image-canvas-editor__character-animation-textarea {
|
||||
grid-column: 1 / -1;
|
||||
--auto-grow-editor-min-height: 8rem;
|
||||
--auto-grow-editor-max-height: 16rem;
|
||||
}
|
||||
|
||||
/* src/index.css:8759 */
|
||||
.image-canvas-editor__character-animation-presets {
|
||||
display: flex;
|
||||
grid-column: 1 / -1;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.38rem;
|
||||
}
|
||||
|
||||
.image-canvas-editor__character-animation-preset {
|
||||
border: 1px solid rgba(148, 163, 184, 0.28);
|
||||
border-radius: 999px;
|
||||
background: #f8fafc;
|
||||
padding: 0.34rem 0.58rem;
|
||||
color: #334155;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
/* src/index.css:8775 */
|
||||
.image-canvas-editor__character-animation-footer {
|
||||
display: grid;
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 0.46rem;
|
||||
}
|
||||
|
||||
.image-canvas-editor__character-animation-submit {
|
||||
grid-column: 3;
|
||||
min-width: 5.45rem;
|
||||
height: 2.25rem;
|
||||
gap: 0.4rem;
|
||||
border: 0;
|
||||
background: var(--image-canvas-brand-fill);
|
||||
color: var(--image-canvas-brand-text-on-fill);
|
||||
}
|
||||
|
||||
/* 窄屏与上面的浮层工具条同档(src/index.css:9020 用 760px):面板落到底部整宽、动作竖排。 */
|
||||
@media (max-width: 720px) {
|
||||
.image-canvas-editor__character-animation-panel {
|
||||
position: fixed;
|
||||
left: 0.75rem !important;
|
||||
right: 0.75rem;
|
||||
top: auto !important;
|
||||
bottom: 0.75rem;
|
||||
width: auto;
|
||||
max-height: min(72vh, 34rem);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.image-canvas-editor__character-animation-footer {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.image-canvas-editor__character-animation-submit {
|
||||
grid-column: auto;
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
justify-self: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { cleanup, render } from '@testing-library/react';
|
||||
import { useState } from 'react';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { ImageCanvasCharacterAnimationPanelView } from '../../../src/components/image-editor/ImageCanvasCharacterAnimationPanelView';
|
||||
import type {
|
||||
CanvasLayer,
|
||||
CharacterAnimationPanelState,
|
||||
} from '../../../src/components/image-editor/ImageCanvasEditorTypes';
|
||||
|
||||
/**
|
||||
* 「生成动画」面板在 AGC 的样式覆盖契约。
|
||||
*
|
||||
* 现象(客户端验收现场):资源卡上点「生成动画」,面板只剩通用 composer 外观——素材缩略图、
|
||||
* 动作预设、`生成NN泥点` 按钮与关闭键全部落回默认流布局,面板里出现一大块空白、预设竖排。
|
||||
* 根因是这套 `image-canvas-editor__character-animation-*` / `__generation-close` /
|
||||
* `__reference-*` 规则只写在网页端整站表 `src/index.css` 里,而 AGC 是独立宿主。
|
||||
*
|
||||
* jsdom 不计算外部 CSS,所以这里不做"渲染出来看观感",而是把两半分别钉死:
|
||||
* 1) DOM 契约:面板真的渲染出这些类名(含 `reference-chip--${tone}` 这种运行时拼出来的);
|
||||
* 2) 样式表契约:从 AGC 入口 `src/main.tsx` 按真实 import 关系解析出"AGC 到底加载了哪些
|
||||
* 样式表",断言上面每个类都至少命中一条规则,并抽查几条撑开布局的关键声明。
|
||||
*
|
||||
* 这是**弱验证**(声明级,不是真机观感)。真机判据:资源卡 → 生成动画 → 缩略图、五个动作
|
||||
* 预设一排、按钮落在面板右下角、右上角有关闭键;窄屏(≤720px)面板落到底部整宽、按钮整宽。
|
||||
*/
|
||||
|
||||
const TEST_DIR = dirname(fileURLToPath(import.meta.url));
|
||||
const AGC_ROOT = resolve(TEST_DIR, '..');
|
||||
const REPO_ROOT = resolve(AGC_ROOT, '..', '..');
|
||||
|
||||
const PANEL_CLASS = 'image-canvas-editor__character-animation-panel';
|
||||
|
||||
type CssRule = { selector: string; body: string };
|
||||
|
||||
/** `@genarrative/<pkg>/<subpath>` → `packages/<pkg>/package.json` 的 exports 指向的真实文件。 */
|
||||
function resolveStyleSpecifier(specifier: string, fromFile: string): string {
|
||||
if (specifier.startsWith('.')) {
|
||||
return resolve(dirname(fromFile), specifier);
|
||||
}
|
||||
const match = /^(@[^/]+\/[^/]+|[^@/][^/]*)(\/.*)?$/u.exec(specifier);
|
||||
expect(match, `样式入口无法解析:${specifier}`).not.toBeNull();
|
||||
const packageName = match![1]!
|
||||
.replace(/^@genarrative\//u, '')
|
||||
.replace(/\//gu, '-');
|
||||
const packageDir = resolve(REPO_ROOT, 'packages', packageName);
|
||||
const manifestPath = resolve(packageDir, 'package.json');
|
||||
if (!existsSync(manifestPath)) {
|
||||
throw new Error(
|
||||
`AGC 新增了未知样式入口「${specifier}」:请把它的解析方式补进本用例的清单。`,
|
||||
);
|
||||
}
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as {
|
||||
exports?: Record<string, string>;
|
||||
};
|
||||
const subpath = match![2] ? `.${match![2]}` : '.';
|
||||
const target = manifest.exports?.[subpath];
|
||||
if (!target) {
|
||||
throw new Error(
|
||||
`样式入口「${specifier}」在 packages/${packageName}/package.json 的 exports 里没有对应项。`,
|
||||
);
|
||||
}
|
||||
return resolve(packageDir, target);
|
||||
}
|
||||
|
||||
/** 从 AGC 入口出发按 import / @import 关系收集它加载的样式表,不手写清单。 */
|
||||
function collectAgcLoadedStyleSheets(): string[] {
|
||||
const entryFile = resolve(AGC_ROOT, 'src/main.tsx');
|
||||
const pending = [
|
||||
...readFileSync(entryFile, 'utf8').matchAll(
|
||||
/import\s+'(?<specifier>[^']+\.css)'/gu,
|
||||
),
|
||||
].map((match) => resolveStyleSpecifier(match.groups!.specifier!, entryFile));
|
||||
|
||||
const loaded: string[] = [];
|
||||
while (pending.length > 0) {
|
||||
const sheet = pending.pop()!;
|
||||
if (loaded.includes(sheet)) {
|
||||
continue;
|
||||
}
|
||||
loaded.push(sheet);
|
||||
const source = readFileSync(sheet, 'utf8');
|
||||
for (const match of source.matchAll(/@import\s+'(?<specifier>[^']+)'/gu)) {
|
||||
const specifier = match.groups!.specifier!;
|
||||
// 由 Vite / Tailwind 插件接管的入口没有实体文件。
|
||||
if (specifier === 'tailwindcss') {
|
||||
continue;
|
||||
}
|
||||
pending.push(resolveStyleSpecifier(specifier, sheet));
|
||||
}
|
||||
}
|
||||
return loaded.sort();
|
||||
}
|
||||
|
||||
/** 够用的规则读取:取每一段 `选择器 { 声明 }`,跳过 `@media` 这类容器本身。 */
|
||||
function readRules(file: string): CssRule[] {
|
||||
const source = readFileSync(file, 'utf8').replace(/\/\*[\s\S]*?\*\//gu, '');
|
||||
const rules: CssRule[] = [];
|
||||
for (const match of source.matchAll(/([^{}]+)\{([^{}]*)\}/gu)) {
|
||||
const selector = match[1]!.trim().replace(/\s+/gu, ' ');
|
||||
if (!selector || selector.startsWith('@')) {
|
||||
continue;
|
||||
}
|
||||
rules.push({ selector, body: match[2]!.trim().replace(/\s+/gu, ' ') });
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
function declarationOf(
|
||||
rules: readonly CssRule[],
|
||||
className: string,
|
||||
property: string,
|
||||
): string | null {
|
||||
const dot = `.${className}`;
|
||||
for (const rule of rules) {
|
||||
const selectors = rule.selector.split(',').map((item) => item.trim());
|
||||
// 只认「这个类自己那一档」的声明:`.cls` 精确出现,或 `.cls 后代`。
|
||||
const owns = selectors.some(
|
||||
(selector) =>
|
||||
selector === dot ||
|
||||
selector.startsWith(`${dot} `) ||
|
||||
selector.startsWith(`${dot}:`) ||
|
||||
selector.startsWith(`${dot}[`) ||
|
||||
selector.endsWith(` ${dot}`),
|
||||
);
|
||||
if (!owns) {
|
||||
continue;
|
||||
}
|
||||
for (const chunk of rule.body.split(';')) {
|
||||
const separator = chunk.indexOf(':');
|
||||
if (separator < 0) {
|
||||
continue;
|
||||
}
|
||||
if (chunk.slice(0, separator).trim() === property) {
|
||||
return chunk
|
||||
.slice(separator + 1)
|
||||
.trim()
|
||||
.replace(/\s+/gu, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function animationPanel() {
|
||||
const sourceLayer: CanvasLayer = {
|
||||
id: 'layer-a',
|
||||
resourceId: 'resource-a',
|
||||
title: 'char_hero_back.png',
|
||||
src: 'data:image/png;base64,Y2hhcmFjdGVy',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 128,
|
||||
height: 128,
|
||||
originalWidth: 128,
|
||||
originalHeight: 128,
|
||||
zIndex: 1,
|
||||
sourceType: 'uploaded',
|
||||
};
|
||||
const initialPanel: CharacterAnimationPanelState = {
|
||||
sourceLayerId: 'layer-a',
|
||||
promptText: '待机动作',
|
||||
resolution: '480p',
|
||||
ratio: 'same',
|
||||
frameCount: 32,
|
||||
durationSeconds: 4,
|
||||
status: 'idle',
|
||||
};
|
||||
function Harness() {
|
||||
const [panel, setPanel] = useState<CharacterAnimationPanelState | null>(
|
||||
initialPanel,
|
||||
);
|
||||
return panel ? (
|
||||
<ImageCanvasCharacterAnimationPanelView
|
||||
panel={panel}
|
||||
sourceLayer={sourceLayer}
|
||||
style={{ left: 12, top: 24 }}
|
||||
price={80}
|
||||
setCharacterAnimationPanel={setPanel}
|
||||
onUpdateDuration={vi.fn()}
|
||||
onSubmit={vi.fn()}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
const { container } = render(<Harness />);
|
||||
return container;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe('「生成动画」面板的 AGC 样式覆盖', () => {
|
||||
it('网页端给了规则的类,AGC 也必须有一条(样式缺口守卫)', () => {
|
||||
const container = animationPanel();
|
||||
const classes = new Set<string>();
|
||||
for (const element of container.querySelectorAll('[class]')) {
|
||||
for (const name of element.classList) {
|
||||
if (name.startsWith('image-canvas-editor__')) {
|
||||
classes.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(classes.size).toBeGreaterThan(8);
|
||||
|
||||
/** 判据只认「网页端有、AGC 没有」这一类:两边都没有的类是按设计不带样式(如纯语义 span)。 */
|
||||
const agcSelectors = collectAgcLoadedStyleSheets()
|
||||
.flatMap(readRules)
|
||||
.map((rule) => rule.selector)
|
||||
.join('\n');
|
||||
const webSelectors = readRules(resolve(REPO_ROOT, 'src/index.css'))
|
||||
.map((rule) => rule.selector)
|
||||
.join('\n');
|
||||
const webStyled = [...classes].filter((name) =>
|
||||
webSelectors.includes(`.${name}`),
|
||||
);
|
||||
expect(
|
||||
webStyled.length,
|
||||
'网页端样式这一次一条都没匹配上:断言退化成空集,守卫失效',
|
||||
).toBeGreaterThan(8);
|
||||
const missing = webStyled.filter(
|
||||
(name) => !agcSelectors.includes(`.${name}`),
|
||||
);
|
||||
expect(
|
||||
missing,
|
||||
`这些类网页端有规则、AGC 一条都没有:${missing.join(', ')}`,
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('撑开面板布局的关键声明逐条在位', () => {
|
||||
const rules = collectAgcLoadedStyleSheets().flatMap(readRules);
|
||||
|
||||
expect(declarationOf(rules, PANEL_CLASS, 'position')).toBe('absolute');
|
||||
expect(declarationOf(rules, PANEL_CLASS, 'width')).toContain('42rem');
|
||||
expect(
|
||||
declarationOf(
|
||||
rules,
|
||||
'image-canvas-editor__generation-composer--character-animation',
|
||||
'grid-template-columns',
|
||||
),
|
||||
).toBe('minmax(0, 1fr)');
|
||||
expect(
|
||||
declarationOf(
|
||||
rules,
|
||||
'image-canvas-editor__character-animation-presets',
|
||||
'display',
|
||||
),
|
||||
).toBe('flex');
|
||||
expect(
|
||||
declarationOf(
|
||||
rules,
|
||||
'image-canvas-editor__character-animation-preset',
|
||||
'border-radius',
|
||||
),
|
||||
).toBe('999px');
|
||||
expect(
|
||||
declarationOf(
|
||||
rules,
|
||||
'image-canvas-editor__character-animation-footer',
|
||||
'grid-template-columns',
|
||||
),
|
||||
).toBe('auto minmax(0, 1fr) auto');
|
||||
expect(
|
||||
declarationOf(
|
||||
rules,
|
||||
'image-canvas-editor__character-animation-submit',
|
||||
'grid-column',
|
||||
),
|
||||
).toBe('3');
|
||||
expect(
|
||||
declarationOf(rules, 'image-canvas-editor__generation-close', 'display'),
|
||||
).toBe('inline-flex');
|
||||
expect(
|
||||
declarationOf(
|
||||
rules,
|
||||
'image-canvas-editor__reference-strip',
|
||||
'grid-column',
|
||||
),
|
||||
).toBe('1 / -1');
|
||||
expect(
|
||||
declarationOf(rules, 'image-canvas-editor__reference-chip', 'width'),
|
||||
).toBe('3.95rem');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user