Browse Source

Merge branch 'master' into feat/offline-friendly

pull/11756/head
Alex Zhu 5 months ago
committed by GitHub
parent
commit
fedcd1a7b2
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 28
      package-lock.json
  2. 2
      package.json
  3. 160
      src/components/Footer/index.tsx
  4. 262
      src/pages/user/login/__snapshots__/login.test.tsx.snap
  5. 2
      src/pages/user/login/login.test.tsx
  6. 109
      tests/setupTests.jsx

28
package-lock.json

@ -56,7 +56,7 @@
"jest-environment-jsdom": "^30.3.0",
"lint-staged": "^16.4.0",
"mockjs": "^1.1.0",
"swagger-ui-dist": "^4.18.1",
"swagger-ui-dist": "^5.32.5",
"tailwindcss": "^4.2.4",
"ts-node": "^10.9.2",
"typescript": "^6.0.3"
@ -6801,6 +6801,14 @@
"react-dom": ">=16.9.0"
}
},
"node_modules/@scarf/scarf": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0"
},
"node_modules/@simple-libs/child-process-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-1.0.2.tgz",
@ -10297,6 +10305,13 @@
"swagger-ui-dist": "^4.18.1"
}
},
"node_modules/@umijs/max-plugin-openapi/node_modules/swagger-ui-dist": {
"version": "4.19.1",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.19.1.tgz",
"integrity": "sha512-n/gFn+R7G/BXWwl5UZLw6F1YgWOlf3zkwGlsPhTMhNtAAolBGKg0JS5b2RKt5NI6/hSopVaSrki2wTIMUDDy2w==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@umijs/max/node_modules/@ant-design/colors": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz",
@ -29902,11 +29917,14 @@
}
},
"node_modules/swagger-ui-dist": {
"version": "4.19.1",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.19.1.tgz",
"integrity": "sha512-n/gFn+R7G/BXWwl5UZLw6F1YgWOlf3zkwGlsPhTMhNtAAolBGKg0JS5b2RKt5NI6/hSopVaSrki2wTIMUDDy2w==",
"version": "5.32.5",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.5.tgz",
"integrity": "sha512-7/FQfWe9A4qoyYFdAwy0chD0uDYidDp/ZT9VQ9LZlgD4AnnHJk8/+ytAA1HkJYOPySmK6helPDdJQMlcumt7HA==",
"dev": true,
"license": "Apache-2.0"
"license": "Apache-2.0",
"dependencies": {
"@scarf/scarf": "=1.4.0"
}
},
"node_modules/swagger2openapi": {
"version": "7.0.8",

2
package.json

@ -81,7 +81,7 @@
"jest-environment-jsdom": "^30.3.0",
"lint-staged": "^16.4.0",
"mockjs": "^1.1.0",
"swagger-ui-dist": "^4.18.1",
"swagger-ui-dist": "^5.32.5",
"tailwindcss": "^4.2.4",
"ts-node": "^10.9.2",
"typescript": "^6.0.3"

160
src/components/Footer/index.tsx

@ -1,6 +1,7 @@
import { GithubOutlined } from '@ant-design/icons';
import { DefaultFooter } from '@ant-design/pro-components';
import packageJson from '@root/package.json';
import { Divider } from 'antd';
import { createStyles } from 'antd-style';
import React from 'react';
const getRepoUrl = () => {
@ -10,66 +11,123 @@ const getRepoUrl = () => {
typeof packageJson.repository === 'string'
? packageJson.repository
: (packageJson.repository as { url: string }).url;
// Parse git URLs like git@github.com:owner/name.git or https://github.com/owner/name.git
const match = repo.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
if (!match) return 'https://github.com/ant-design/ant-design-pro';
return `https://github.com/${match[1]}/${match[2]}`;
};
const REPO_URL = getRepoUrl();
// Git commit hash, resolved at build time from env vars or git
const COMMIT_HASH = process.env.COMMIT_HASH || '';
const useStyles = createStyles(({ token, css }) => ({
footer: css`
padding: 16px 24px;
text-align: center;
color: ${token.colorTextDescription};
font-size: ${token.fontSizeSM}px;
line-height: ${token.lineHeight};
background: transparent;
`,
copyright: css`
margin-bottom: 6px;
`,
link: css`
color: ${token.colorTextDescription};
text-decoration: none;
transition: color ${token.motionDurationMid};
&:hover {
color: ${token.colorText};
}
`,
meta: css`
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 6px 12px;
font-family: ${token.fontFamilyCode};
font-size: ${token.fontSizeSM - 1}px;
`,
group: css`
display: inline-flex;
align-items: center;
gap: 4px;
`,
label: css`
color: ${token.colorTextQuaternary};
`,
divider: css`
display: inline-block;
vertical-align: middle;
`,
}));
const Footer: React.FC = () => {
const { styles } = useStyles();
const year = new Date().getFullYear();
return (
<DefaultFooter
copyright={false}
style={{
background: 'none',
}}
links={[
{
key: 'version',
title: `v${__APP_VERSION__}`,
href: REPO_URL,
blankTarget: true,
},
{
key: 'umi',
title: `Umi ${__UMI_VERSION__}`,
href: 'https://umijs.org/',
blankTarget: true,
},
{
key: 'utoo',
title: `Utoo ${__UTOO_VERSION__}`,
href: 'https://utoo.land',
blankTarget: true,
},
...(COMMIT_HASH
? [
{
key: 'commit',
title: COMMIT_HASH.slice(0, 7),
href: `${REPO_URL}/commit/${COMMIT_HASH}`,
blankTarget: true,
},
]
: []),
{
key: 'repo',
title: (
<>
<GithubOutlined style={{ marginRight: 8 }} />
Ant Design Pro
</>
),
href: REPO_URL,
blankTarget: true,
},
]}
/>
<div className={styles.footer}>
<div className={styles.copyright}>Ant Design Pro &copy; {year}</div>
<div className={styles.meta}>
<span className={styles.group}>
<span className={styles.label}>ver</span>
<a
className={styles.link}
href={REPO_URL}
target="_blank"
rel="noopener noreferrer"
>
{__APP_VERSION__}
</a>
{COMMIT_HASH && (
<a
className={styles.link}
href={`${REPO_URL}/commit/${COMMIT_HASH}`}
target="_blank"
rel="noopener noreferrer"
>
{COMMIT_HASH.slice(0, 7)}
</a>
)}
</span>
<Divider type="vertical" className={styles.divider} />
<span className={styles.group}>
<span className={styles.label}>Umi</span>
<a
className={styles.link}
href="https://umijs.org/"
target="_blank"
rel="noopener noreferrer"
>
{__UMI_VERSION__}
</a>
</span>
<Divider type="vertical" className={styles.divider} />
<span className={styles.group}>
<span className={styles.label}>Utoo</span>
<a
className={styles.link}
href="https://utoo.land"
target="_blank"
rel="noopener noreferrer"
>
{__UTOO_VERSION__}
</a>
</span>
<Divider type="vertical" className={styles.divider} />
<a
className={styles.link}
href={REPO_URL}
target="_blank"
rel="noopener noreferrer"
>
<GithubOutlined style={{ marginRight: 4 }} />
GitHub
</a>
</div>
</div>
);
};

262
src/pages/user/login/__snapshots__/login.test.tsx.snap

@ -454,77 +454,110 @@ exports[`Login Page should login success 1`] = `
</div>
</div>
</div>
<footer
class="ant-layout-footer"
data-testid="pro-layout-footer"
style="padding: 0px; background: none;"
<div
class="acss-kiaa3f"
>
<div
class="ant-pro-global-footer"
data-testid="pro-global-footer"
class="acss-1iouf57"
>
<div
class="ant-pro-global-footer-list"
Ant Design Pro © 2026
</div>
<div
class="acss-1f94i0u"
>
<span
class="acss-84i4t5"
>
<span
class="acss-vpsqlh"
>
ver
</span>
<a
class="ant-pro-global-footer-list-link"
class="acss-1opaod3"
href="https://github.com/ant-design/ant-design-pro"
rel="noreferrer"
rel="noopener noreferrer"
target="_blank"
title="version"
>
vtest
test
</a>
</span>
<div
class="ant-divider css-var-_r_9_ ant-divider-vertical ant-divider-rail acss-10ntr2d"
role="separator"
/>
<span
class="acss-84i4t5"
>
<span
class="acss-vpsqlh"
>
Umi
</span>
<a
class="ant-pro-global-footer-list-link"
class="acss-1opaod3"
href="https://umijs.org/"
rel="noreferrer"
rel="noopener noreferrer"
target="_blank"
title="umi"
>
Umi 4.6.49
4.6.49
</a>
</span>
<div
class="ant-divider css-var-_r_9_ ant-divider-vertical ant-divider-rail acss-10ntr2d"
role="separator"
/>
<span
class="acss-84i4t5"
>
<span
class="acss-vpsqlh"
>
Utoo
</span>
<a
class="ant-pro-global-footer-list-link"
class="acss-1opaod3"
href="https://utoo.land"
rel="noreferrer"
rel="noopener noreferrer"
target="_blank"
title="utoo"
>
Utoo 1.4.2-alpha.0
1.4.2-alpha.0
</a>
<a
class="ant-pro-global-footer-list-link"
href="https://github.com/ant-design/ant-design-pro"
rel="noreferrer"
target="_blank"
title="repo"
</span>
<div
class="ant-divider css-var-_r_9_ ant-divider-vertical ant-divider-rail acss-10ntr2d"
role="separator"
/>
<a
class="acss-1opaod3"
href="https://github.com/ant-design/ant-design-pro"
rel="noopener noreferrer"
target="_blank"
>
<span
aria-label="github"
class="anticon anticon-github"
role="img"
style="margin-right: 4px;"
>
<span
aria-label="github"
class="anticon anticon-github"
role="img"
style="margin-right: 8px;"
<svg
aria-hidden="true"
data-icon="github"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<svg
aria-hidden="true"
data-icon="github"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9a127.5 127.5 0 0138.1 91v112.5c.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z"
/>
</svg>
</span>
Ant Design Pro
</a>
</div>
<path
d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9a127.5 127.5 0 0138.1 91v112.5c.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z"
/>
</svg>
</span>
GitHub
</a>
</div>
</footer>
</div>
</div>
</div>
</DocumentFragment>
@ -983,77 +1016,110 @@ exports[`Login Page should show login form 1`] = `
</div>
</div>
</div>
<footer
class="ant-layout-footer"
data-testid="pro-layout-footer"
style="padding: 0px; background: none;"
<div
class="acss-kiaa3f"
>
<div
class="ant-pro-global-footer"
data-testid="pro-global-footer"
class="acss-1iouf57"
>
<div
class="ant-pro-global-footer-list"
Ant Design Pro © 2026
</div>
<div
class="acss-1f94i0u"
>
<span
class="acss-84i4t5"
>
<span
class="acss-vpsqlh"
>
ver
</span>
<a
class="ant-pro-global-footer-list-link"
class="acss-1opaod3"
href="https://github.com/ant-design/ant-design-pro"
rel="noreferrer"
rel="noopener noreferrer"
target="_blank"
title="version"
>
vtest
test
</a>
</span>
<div
class="ant-divider css-var-_r_0_ ant-divider-vertical ant-divider-rail acss-10ntr2d"
role="separator"
/>
<span
class="acss-84i4t5"
>
<span
class="acss-vpsqlh"
>
Umi
</span>
<a
class="ant-pro-global-footer-list-link"
class="acss-1opaod3"
href="https://umijs.org/"
rel="noreferrer"
rel="noopener noreferrer"
target="_blank"
title="umi"
>
Umi 4.6.49
4.6.49
</a>
</span>
<div
class="ant-divider css-var-_r_0_ ant-divider-vertical ant-divider-rail acss-10ntr2d"
role="separator"
/>
<span
class="acss-84i4t5"
>
<span
class="acss-vpsqlh"
>
Utoo
</span>
<a
class="ant-pro-global-footer-list-link"
class="acss-1opaod3"
href="https://utoo.land"
rel="noreferrer"
rel="noopener noreferrer"
target="_blank"
title="utoo"
>
Utoo 1.4.2-alpha.0
1.4.2-alpha.0
</a>
<a
class="ant-pro-global-footer-list-link"
href="https://github.com/ant-design/ant-design-pro"
rel="noreferrer"
target="_blank"
title="repo"
</span>
<div
class="ant-divider css-var-_r_0_ ant-divider-vertical ant-divider-rail acss-10ntr2d"
role="separator"
/>
<a
class="acss-1opaod3"
href="https://github.com/ant-design/ant-design-pro"
rel="noopener noreferrer"
target="_blank"
>
<span
aria-label="github"
class="anticon anticon-github"
role="img"
style="margin-right: 4px;"
>
<span
aria-label="github"
class="anticon anticon-github"
role="img"
style="margin-right: 8px;"
<svg
aria-hidden="true"
data-icon="github"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<svg
aria-hidden="true"
data-icon="github"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9a127.5 127.5 0 0138.1 91v112.5c.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z"
/>
</svg>
</span>
Ant Design Pro
</a>
</div>
<path
d="M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9a127.5 127.5 0 0138.1 91v112.5c.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z"
/>
</svg>
</span>
GitHub
</a>
</div>
</footer>
</div>
</div>
</div>
</DocumentFragment>

2
src/pages/user/login/login.test.tsx

@ -81,7 +81,7 @@ describe('Login Page', () => {
await (await rootContainer.findByText('Login')).click();
// Wait for login to succeed and navigate to home page
await rootContainer.findByText('Ant Design Pro', undefined, {
await rootContainer.findByText(/Ant Design Pro/, undefined, {
timeout: 10000,
});

109
tests/setupTests.jsx

@ -1,56 +1,71 @@
import { defaultConfig } from 'antd/lib/theme/internal';
import { defaultConfig } from 'antd/lib/theme/internal';
defaultConfig.hashed = false;
const store = {};
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn(),
getItem: jest.fn((key) => store[key] ?? null),
setItem: jest.fn((key, value) => {
store[key] = String(value);
}),
removeItem: jest.fn((key) => {
delete store[key];
}),
clear: jest.fn(() => {
Object.keys(store).forEach((key) => {
delete store[key];
});
}),
};
global.localStorage = localStorageMock;
globalThis.localStorage = localStorageMock;
Object.defineProperty(URL, 'createObjectURL', {
writable: true,
value: jest.fn(),
});
class Worker {
class MockWorker {
constructor(stringUrl) {
this.url = stringUrl;
this.onmessage = () => {};
this.onmessage = null;
}
postMessage(msg) {
this.onmessage(msg);
if (typeof this.onmessage === 'function') {
this.onmessage({ data: msg });
}
}
terminate() {}
addEventListener() {}
removeEventListener() {}
}
window.Worker = Worker;
// Polyfill MessageChannel for environments (like jest/jsdom) that don't provide it
if (typeof global.MessageChannel === 'undefined') {
globalThis.Worker = MockWorker;
if (typeof globalThis.MessageChannel === 'undefined') {
class PolyMessageChannel {
constructor() {
const channel = this;
this.port1 = {
onmessage: null,
postMessage(msg) {
setTimeout(() => {
if (
channel.port2 &&
typeof channel.port2.onmessage === 'function'
) {
if (typeof channel.port2.onmessage === 'function') {
channel.port2.onmessage({ data: msg });
}
}, 0);
},
};
this.port2 = {
onmessage: null,
postMessage(msg) {
setTimeout(() => {
if (
channel.port1 &&
typeof channel.port1.onmessage === 'function'
) {
if (typeof channel.port1.onmessage === 'function') {
channel.port1.onmessage({ data: msg });
}
}, 0);
@ -59,39 +74,28 @@ if (typeof global.MessageChannel === 'undefined') {
}
}
global.MessageChannel = PolyMessageChannel;
if (typeof window !== 'undefined') {
window.MessageChannel = PolyMessageChannel;
}
globalThis.MessageChannel = PolyMessageChannel;
}
if (typeof window !== 'undefined') {
// ref: https://github.com/ant-design/ant-design/issues/18774
if (!window.matchMedia) {
Object.defineProperty(global.window, 'matchMedia', {
writable: true,
configurable: true,
value: jest.fn(() => ({
matches: false,
addListener: jest.fn(),
removeListener: jest.fn(),
})),
});
}
if (!window.matchMedia) {
Object.defineProperty(global.window, 'matchMedia', {
writable: true,
configurable: true,
value: jest.fn((query) => ({
matches: query.includes('max-width'),
addListener: jest.fn(),
removeListener: jest.fn(),
})),
});
}
if (typeof window !== 'undefined' && !window.matchMedia) {
Object.defineProperty(window, 'matchMedia', {
writable: true,
configurable: true,
value: jest.fn((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
}
const errorLog = console.error;
Object.defineProperty(global.window.console, 'error', {
const originalError = console.error;
Object.defineProperty(window.console, 'error', {
writable: true,
configurable: true,
value: (...rest) => {
@ -103,12 +107,11 @@ Object.defineProperty(global.window.console, 'error', {
) {
return;
}
errorLog(...rest);
originalError(...rest);
},
});
// Mock ResizeObserver
global.ResizeObserver = class ResizeObserver {
globalThis.ResizeObserver = class {
observe() {}
unobserve() {}
disconnect() {}

Loading…
Cancel
Save