将作品卡积分激励领取按钮迁移到 PlatformActionButton 保留积分激励按钮紧凑布局和移动端跨列样式 补充领取按钮公共组件和 CSS 覆盖断言 更新 PlatformUiKit 文档和 Hermes 决策记录
230 lines
8.9 KiB
TypeScript
230 lines
8.9 KiB
TypeScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
function readIndexCss() {
|
|
return fs.readFileSync(path.resolve(process.cwd(), 'src/index.css'), 'utf8');
|
|
}
|
|
|
|
function getCssBlock(source: string, selector: string) {
|
|
const selectorIndex = source.indexOf(selector);
|
|
expect(selectorIndex, `${selector} should exist in src/index.css`).toBeGreaterThanOrEqual(0);
|
|
|
|
const openBraceIndex = source.indexOf('{', selectorIndex);
|
|
expect(openBraceIndex, `${selector} should open a CSS block`).toBeGreaterThanOrEqual(0);
|
|
|
|
let depth = 0;
|
|
for (let index = openBraceIndex; index < source.length; index += 1) {
|
|
const char = source[index];
|
|
if (char === '{') {
|
|
depth += 1;
|
|
} else if (char === '}') {
|
|
depth -= 1;
|
|
if (depth === 0) {
|
|
return source.slice(openBraceIndex + 1, index);
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new Error(`${selector} block is not closed`);
|
|
}
|
|
|
|
describe('index stylesheet unread dots', () => {
|
|
it('hides the outer page scrollbar without changing inner scroll helpers', () => {
|
|
const css = readIndexCss();
|
|
|
|
const rootBlock = getCssBlock(css, 'html,\nbody,\n#root');
|
|
expect(rootBlock).toContain('overflow-x: hidden;');
|
|
expect(rootBlock).toContain('-ms-overflow-style: none;');
|
|
expect(rootBlock).toContain('scrollbar-width: none;');
|
|
expect(css).toContain(
|
|
'html,\nbody,\n#root,\n.platform-viewport-shell,\n.platform-tab-panel,\n.platform-page-stage,\n.unified-creation-page,\n.platform-work-detail__scroll',
|
|
);
|
|
|
|
const webkitRootBlock = getCssBlock(
|
|
css,
|
|
'html::-webkit-scrollbar,\nbody::-webkit-scrollbar,\n#root::-webkit-scrollbar',
|
|
);
|
|
expect(webkitRootBlock).toContain('display: none;');
|
|
expect(webkitRootBlock).toContain('width: 0;');
|
|
expect(webkitRootBlock).toContain('height: 0;');
|
|
expect(css).toContain('.platform-viewport-shell::-webkit-scrollbar');
|
|
expect(css).toContain('.platform-tab-panel::-webkit-scrollbar');
|
|
expect(css).toContain('.platform-page-stage::-webkit-scrollbar');
|
|
expect(css).toContain('.unified-creation-page::-webkit-scrollbar');
|
|
expect(css).toContain('.platform-work-detail__scroll::-webkit-scrollbar');
|
|
|
|
expect(css).toContain('.scrollbar-hide');
|
|
expect(css).toContain('::-webkit-scrollbar-thumb');
|
|
});
|
|
|
|
it('uses the platform fill for root background exposed by mobile keyboard shift', () => {
|
|
const css = readIndexCss();
|
|
|
|
const keyboardRootBlock = getCssBlock(
|
|
css,
|
|
"html[data-mobile-keyboard-open='true'],\nhtml[data-mobile-keyboard-open='true'] body,\nhtml[data-mobile-keyboard-open='true'] #root",
|
|
);
|
|
expect(keyboardRootBlock).toContain('--platform-keyboard-exposed-fill');
|
|
expect(keyboardRootBlock).toContain('#fffdf9');
|
|
expect(keyboardRootBlock).not.toContain('#0a0a0a');
|
|
});
|
|
|
|
it('does not globally transform the platform shell while the mobile keyboard is open', () => {
|
|
const css = readIndexCss();
|
|
|
|
const platformShellBlock = getCssBlock(
|
|
css,
|
|
'.platform-viewport-shell {\n height',
|
|
);
|
|
expect(platformShellBlock).toContain('--platform-layout-viewport-height');
|
|
expect(platformShellBlock).not.toContain('translate3d');
|
|
expect(platformShellBlock).not.toContain('--platform-keyboard-focus-offset');
|
|
});
|
|
|
|
it('uses warm brown tokens for draft unread markers instead of red literals', () => {
|
|
const css = readIndexCss();
|
|
|
|
expect(css).toContain('--platform-unread-dot-fill: #8b5a3d;');
|
|
expect(css).toContain('--platform-unread-dot-glow: rgba(139, 90, 61, 0.34);');
|
|
expect(css).toContain('--platform-unread-dot-fill: #d6a27c;');
|
|
expect(css).toContain('--platform-unread-dot-glow: rgba(214, 162, 124, 0.24);');
|
|
|
|
for (const selector of [
|
|
'.creation-work-card__unread-dot',
|
|
'.platform-nav-unread-dot',
|
|
]) {
|
|
const block = getCssBlock(css, selector);
|
|
|
|
expect(block).toContain('background: var(--platform-unread-dot-fill);');
|
|
expect(block).toContain('var(--platform-unread-dot-glow)');
|
|
expect(block).not.toContain('#b64a35');
|
|
expect(block).not.toContain('rgba(239, 68, 68');
|
|
}
|
|
});
|
|
|
|
it('keeps the creation shelf share button on a visible surface', () => {
|
|
const css = readIndexCss();
|
|
const block = getCssBlock(css, '.creation-work-card__quick-action-button');
|
|
|
|
expect(block).toContain('border: 1px solid');
|
|
expect(block).toContain('background: color-mix(');
|
|
expect(block).toContain('var(--platform-neutral-bg)');
|
|
expect(block).toContain('var(--platform-cool-bg)');
|
|
expect(block).not.toContain('background: transparent;');
|
|
});
|
|
|
|
it('keeps the creation shelf point incentive action button compact', () => {
|
|
const css = readIndexCss();
|
|
const surfaceBlock = getCssBlock(
|
|
css,
|
|
'.creation-work-card-incentive__button.platform-button',
|
|
);
|
|
const compactButtonBlock = getCssBlock(
|
|
css,
|
|
'.creation-work-card-incentive__button.platform-button {\n display: inline-flex;',
|
|
);
|
|
const mobileQueryIndex = css.indexOf('@media (max-width: 639px)');
|
|
const mobileSelectorIndex = css.indexOf(
|
|
'.creation-work-card-incentive__button.platform-button',
|
|
mobileQueryIndex,
|
|
);
|
|
|
|
expect(surfaceBlock).toContain('border: 1px solid');
|
|
expect(surfaceBlock).toContain('background: color-mix(');
|
|
expect(compactButtonBlock).toContain('min-height: 2.55rem;');
|
|
expect(compactButtonBlock).toContain('font-size: 0.68rem;');
|
|
expect(mobileSelectorIndex).toBeGreaterThanOrEqual(0);
|
|
});
|
|
});
|
|
|
|
describe('index stylesheet draft mobile cards', () => {
|
|
it('disables long-press text selection on the whole mobile draft tab', () => {
|
|
const css = readIndexCss();
|
|
const mobileQueryIndex = css.indexOf('@media (max-width: 639px)');
|
|
expect(mobileQueryIndex).toBeGreaterThanOrEqual(0);
|
|
|
|
const draftPanelSelector =
|
|
'#platform-tab-panel-saves,\n #platform-tab-panel-saves *';
|
|
const draftPanelSelectorIndex = css.indexOf(draftPanelSelector, mobileQueryIndex);
|
|
expect(draftPanelSelectorIndex).toBeGreaterThanOrEqual(0);
|
|
|
|
const draftPanelBlock = getCssBlock(css, draftPanelSelector);
|
|
expect(draftPanelBlock).toContain('-webkit-user-select: none;');
|
|
expect(draftPanelBlock).toContain('user-select: none;');
|
|
expect(draftPanelBlock).toContain('-webkit-touch-callout: none;');
|
|
|
|
const editableSelector =
|
|
"#platform-tab-panel-saves :is(input, textarea, [contenteditable='true'])";
|
|
const editableSelectorIndex = css.indexOf(editableSelector, mobileQueryIndex);
|
|
expect(editableSelectorIndex).toBeGreaterThanOrEqual(0);
|
|
|
|
const editableBlock = getCssBlock(css, editableSelector);
|
|
expect(editableBlock).toContain('-webkit-user-select: text;');
|
|
expect(editableBlock).toContain('user-select: text;');
|
|
expect(editableBlock).toContain('-webkit-touch-callout: default;');
|
|
});
|
|
});
|
|
|
|
describe('index stylesheet creation agent hero contrast', () => {
|
|
it('keeps dark creation progress hero text light inside remap surfaces', () => {
|
|
const css = readIndexCss();
|
|
|
|
expect(css).toContain('.platform-remap-surface');
|
|
expect(css).toContain('.creation-agent-hero');
|
|
|
|
const labelBlock = getCssBlock(
|
|
css,
|
|
':where(.creation-agent-hero__summary, .creation-agent-hero__progress-label)',
|
|
);
|
|
expect(labelBlock).toContain('rgba(255, 255, 255, 0.76) !important');
|
|
|
|
const valueBlock = getCssBlock(css, '.creation-agent-hero__progress-value');
|
|
expect(valueBlock).toContain('rgba(255, 255, 255, 0.92) !important');
|
|
|
|
const hintBlock = getCssBlock(css, '.creation-agent-hero__progress-hint');
|
|
expect(hintBlock).toContain('rgba(255, 255, 255, 0.72) !important');
|
|
});
|
|
});
|
|
|
|
describe('index stylesheet recommend runtime cover', () => {
|
|
it('keeps the card cover above embedded runtime and only fades it when ready', () => {
|
|
const css = readIndexCss();
|
|
|
|
const viewportBlock = getCssBlock(
|
|
css,
|
|
'.platform-recommend-runtime-viewport',
|
|
);
|
|
expect(viewportBlock).toContain('z-index: 1;');
|
|
expect(viewportBlock).toContain('isolation: isolate;');
|
|
|
|
const coverBlock = getCssBlock(css, '.platform-recommend-runtime-cover');
|
|
expect(coverBlock).toContain('z-index: 30;');
|
|
expect(coverBlock).toContain('isolation: isolate;');
|
|
expect(coverBlock).not.toContain('transition: opacity');
|
|
|
|
const loadingBlock = getCssBlock(
|
|
css,
|
|
'.platform-recommend-runtime-loading',
|
|
);
|
|
expect(loadingBlock).toContain('position: absolute;');
|
|
expect(loadingBlock).toContain('z-index: 4;');
|
|
|
|
const loadingAnimationBlock = getCssBlock(
|
|
css,
|
|
'.platform-recommend-runtime-loading::before',
|
|
);
|
|
expect(loadingAnimationBlock).toContain(
|
|
'animation: platform-recommend-runtime-loading 1.15s ease-in-out infinite;',
|
|
);
|
|
expect(css).toContain('@keyframes platform-recommend-runtime-loading');
|
|
|
|
const hiddenCoverBlock = getCssBlock(
|
|
css,
|
|
'.platform-recommend-runtime-cover--hidden',
|
|
);
|
|
expect(hiddenCoverBlock).toContain('transition: opacity 420ms ease;');
|
|
});
|
|
});
|