enhance: keep show clearIcon when in mobile view (#53576)

* feat: The clearIcon  of the Select component remain displayed when hover=none device

* chore: tmp of mobile test

* test: support mobile snapshot

* test: adjust logic

* chore: force trigger

* chore: rm is mobile config

* chore: update enable check

* chore: clean up

* chore: force trigger

* chore: debug for query

* chore: trigger info

* test: force disable mobile style

---------

Co-authored-by: 刘欢 <lh01217311@antgroup.com>
Co-authored-by: 二货机器人 <smith3816@gmail.com>
This commit is contained in:
EmilyyyLiu 2025-04-24 14:21:02 +08:00 committed by GitHub
parent c1154c08e9
commit e28247bf0c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 91 additions and 41 deletions

View File

@ -1,5 +1,7 @@
import { imageDemoTest } from '../../../tests/shared/imageTest'; import { imageDemoTest } from '../../../tests/shared/imageTest';
describe('Select image', () => { describe('Select image', () => {
imageDemoTest('select'); imageDemoTest('select', {
mobile: ['basic.tsx'],
});
}); });

View File

@ -72,6 +72,14 @@ const getSearchInputWithoutBorderStyle: GenerateStyle<SelectToken, CSSObject> =
const genBaseStyle: GenerateStyle<SelectToken> = (token) => { const genBaseStyle: GenerateStyle<SelectToken> = (token) => {
const { antCls, componentCls, inputPaddingHorizontalBase, iconCls } = token; const { antCls, componentCls, inputPaddingHorizontalBase, iconCls } = token;
const hoverShowClearStyle: CSSObject = {
[`${componentCls}-clear`]: {
opacity: 1,
background: token.colorBgBase,
borderRadius: '50%',
},
};
return { return {
[componentCls]: { [componentCls]: {
...resetComponent(token), ...resetComponent(token),
@ -198,11 +206,8 @@ const genBaseStyle: GenerateStyle<SelectToken> = (token) => {
}, },
}, },
[`&:hover ${componentCls}-clear`]: { '@media(hover:none)': hoverShowClearStyle,
opacity: 1, '&:hover': hoverShowClearStyle,
background: token.colorBgBase,
borderRadius: '50%',
},
}, },
// ========================= Feedback ========================== // ========================= Feedback ==========================

View File

@ -1,6 +1,7 @@
// jest-puppeteer.config.js // jest-puppeteer.config.js
module.exports = { module.exports = {
launch: { launch: {
headless: 'new',
ignoreDefaultArgs: ['--disable-extensions'], ignoreDefaultArgs: ['--disable-extensions'],
args: [ args: [
// Required for Docker version of Puppeteer // Required for Docker version of Puppeteer

View File

@ -289,7 +289,7 @@
"prettier": "^3.4.1", "prettier": "^3.4.1",
"pretty-format": "^29.7.0", "pretty-format": "^29.7.0",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"puppeteer": "^24.0.0", "puppeteer": "^24.7.1",
"rc-footer": "^0.6.8", "rc-footer": "^0.6.8",
"rc-tween-one": "^3.0.6", "rc-tween-one": "^3.0.6",
"rc-virtual-list": "^3.17.0", "rc-virtual-list": "^3.17.0",

View File

@ -8,8 +8,8 @@ import fse from 'fs-extra';
import { globSync } from 'glob'; import { globSync } from 'glob';
import { JSDOM } from 'jsdom'; import { JSDOM } from 'jsdom';
import MockDate from 'mockdate'; import MockDate from 'mockdate';
import type { HTTPRequest, Viewport } from 'puppeteer';
import rcWarning from 'rc-util/lib/warning'; import rcWarning from 'rc-util/lib/warning';
import type { HTTPRequest } from 'puppeteer';
import ReactDOMServer from 'react-dom/server'; import ReactDOMServer from 'react-dom/server';
import { App, ConfigProvider, theme } from '../../components'; import { App, ConfigProvider, theme } from '../../components';
@ -32,6 +32,7 @@ interface ImageTestOptions {
onlyViewport?: boolean; onlyViewport?: boolean;
ssr?: boolean; ssr?: boolean;
openTriggerClassName?: string; openTriggerClassName?: string;
mobile?: boolean;
} }
// eslint-disable-next-line jest/no-export // eslint-disable-next-line jest/no-export
@ -109,9 +110,14 @@ export default function imageTest(
container = doc.querySelector<HTMLDivElement>('#root')!; container = doc.querySelector<HTMLDivElement>('#root')!;
}); });
function test(name: string, suffix: string, themedComponent: React.ReactElement) { function test(name: string, suffix: string, themedComponent: React.ReactElement, mobile = false) {
it(name, async () => { it(name, async () => {
await page.setViewport({ width: 800, height: 600 }); const sharedViewportConfig: Partial<Viewport> = {
isMobile: mobile,
hasTouch: mobile,
};
await page.setViewport({ width: 800, height: 600, ...sharedViewportConfig });
const onRequestHandle = (request: HTTPRequest) => { const onRequestHandle = (request: HTTPRequest) => {
if (['image'].includes(request.resourceType())) { if (['image'].includes(request.resourceType())) {
@ -166,6 +172,11 @@ export default function imageTest(
unmount(); unmount();
} }
// Remove mobile css for hardcode since CI will always think as mobile
if (!mobile) {
styleStr = styleStr.replace(/@media\(hover:\s*none\)/g, '@media(hover:not-valid)');
}
if (openTriggerClassName) { if (openTriggerClassName) {
styleStr += `<style> styleStr += `<style>
.${openTriggerClassName} { .${openTriggerClassName} {
@ -211,7 +222,7 @@ export default function imageTest(
Please consider using \`onlyViewport: ["filename.tsx"]\`, read more: https://github.com/ant-design/ant-design/pull/52053`, Please consider using \`onlyViewport: ["filename.tsx"]\`, read more: https://github.com/ant-design/ant-design/pull/52053`,
); );
await page.setViewport({ width: 800, height: bodyHeight }); await page.setViewport({ width: 800, height: bodyHeight, ...sharedViewportConfig });
} }
const image = await page.screenshot({ const image = await page.screenshot({
@ -225,29 +236,35 @@ export default function imageTest(
}); });
} }
Object.entries(themes).forEach(([key, algorithm]) => { if (!options.mobile) {
const configTheme = { Object.entries(themes).forEach(([key, algorithm]) => {
algorithm, const configTheme = {
token: { algorithm,
fontFamily: 'Arial', token: {
}, fontFamily: 'Arial',
}; },
};
test( test(
`component image screenshot should correct ${key}`, `component image screenshot should correct ${key}`,
`.${key}`, `.${key}`,
<div style={{ background: key === 'dark' ? '#000' : '', padding: `24px 12px` }} key={key}> <div style={{ background: key === 'dark' ? '#000' : '', padding: `24px 12px` }} key={key}>
<ConfigProvider theme={configTheme}>{component}</ConfigProvider> <ConfigProvider theme={configTheme}>{component}</ConfigProvider>
</div>, </div>,
); );
test( test(
`[CSS Var] component image screenshot should correct ${key}`, `[CSS Var] component image screenshot should correct ${key}`,
`.${key}.css-var`, `.${key}.css-var`,
<div style={{ background: key === 'dark' ? '#000' : '', padding: `24px 12px` }} key={key}> <div style={{ background: key === 'dark' ? '#000' : '', padding: `24px 12px` }} key={key}>
<ConfigProvider theme={{ ...configTheme, cssVar: true }}>{component}</ConfigProvider> <ConfigProvider theme={{ ...configTheme, cssVar: true }}>{component}</ConfigProvider>
</div>, </div>,
); );
}); });
// Mobile Snapshot
} else {
test(identifier, `.mobile`, component, true);
}
} }
type Options = { type Options = {
@ -257,6 +274,7 @@ type Options = {
ssr?: boolean; ssr?: boolean;
/** Open Trigger to check the popup render */ /** Open Trigger to check the popup render */
openTriggerClassName?: string; openTriggerClassName?: string;
mobile?: string[];
}; };
// eslint-disable-next-line jest/no-export // eslint-disable-next-line jest/no-export
@ -266,25 +284,49 @@ export function imageDemoTest(component: string, options: Options = {}) {
(file) => !file.includes('_semantic'), (file) => !file.includes('_semantic'),
); );
const mobileDemos: [file: string, node: any][] = [];
const getTestOption = (file: string) => ({
onlyViewport:
options.onlyViewport === true ||
(Array.isArray(options.onlyViewport) && options.onlyViewport.some((c) => file.endsWith(c))),
ssr: options.ssr,
openTriggerClassName: options.openTriggerClassName,
});
files.forEach((file) => { files.forEach((file) => {
if (Array.isArray(options.skip) && options.skip.some((c) => file.endsWith(c))) { if (Array.isArray(options.skip) && options.skip.some((c) => file.endsWith(c))) {
describeMethod = describe.skip; describeMethod = describe.skip;
} else { } else {
describeMethod = describe; describeMethod = describe;
} }
describeMethod(`Test ${file} image`, () => { describeMethod(`Test ${file} image`, () => {
let Demo = require(`../../${file}`).default; let Demo = require(`../../${file}`).default;
if (typeof Demo === 'function') { if (typeof Demo === 'function') {
Demo = <Demo />; Demo = <Demo />;
} }
imageTest(Demo, `${component}-${path.basename(file, '.tsx')}`, { imageTest(Demo, `${component}-${path.basename(file, '.tsx')}`, getTestOption(file));
onlyViewport:
options.onlyViewport === true || // Check if need mobile test
(Array.isArray(options.onlyViewport) && if ((options.mobile || []).some((c) => file.endsWith(c))) {
options.onlyViewport.some((c) => file.endsWith(c))), mobileDemos.push([file, Demo]);
ssr: options.ssr, }
openTriggerClassName: options.openTriggerClassName,
});
}); });
}); });
if (mobileDemos.length) {
describeMethod(`Test mobile image`, () => {
beforeAll(async () => {
await jestPuppeteer.resetPage();
});
mobileDemos.forEach(([file, Demo]) => {
imageTest(Demo, `${component}-${path.basename(file, '.tsx')}`, {
...getTestOption(file),
mobile: true,
});
});
});
}
} }