8 changed files with 158 additions and 6 deletions
@ -0,0 +1,97 @@ |
|||||
|
const js = require('@eslint/js'); |
||||
|
const tseslint = require('typescript-eslint'); |
||||
|
const react = require('eslint-plugin-react'); |
||||
|
const reactHooks = require('eslint-plugin-react-hooks'); |
||||
|
const globals = require('globals'); |
||||
|
|
||||
|
/** |
||||
|
* 自定义规则:禁止在组件/业务代码内写死十六进制颜色字面量。 |
||||
|
* 对齐 AGENTS.md 配色禁令——颜色必须统一从 src/theme/colors.ts 导入。 |
||||
|
* 例外:theme/colors.ts 本身是设计令牌唯一来源,豁免。 |
||||
|
*/ |
||||
|
const noHardcodedHexColor = { |
||||
|
meta: { |
||||
|
type: 'problem', |
||||
|
docs: { |
||||
|
description: '禁止在组件内写死十六进制颜色,统一从 src/theme/colors.ts 导入', |
||||
|
}, |
||||
|
schema: [], |
||||
|
messages: { |
||||
|
noHex: |
||||
|
'禁止写死十六进制颜色字面量 "{{value}}",请从 src/theme/colors.ts 导入颜色 token。', |
||||
|
}, |
||||
|
}, |
||||
|
create(context) { |
||||
|
const hexRe = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/; |
||||
|
return { |
||||
|
Literal(node) { |
||||
|
if (typeof node.value === 'string' && hexRe.test(node.value)) { |
||||
|
context.report({ node, messageId: 'noHex', data: { value: node.value } }); |
||||
|
} |
||||
|
}, |
||||
|
}; |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
module.exports = tseslint.config( |
||||
|
{ |
||||
|
ignores: [ |
||||
|
'node_modules/**', |
||||
|
'.expo/**', |
||||
|
'dist/**', |
||||
|
'coverage/**', |
||||
|
'figma_out/**', |
||||
|
'*.mjs', |
||||
|
'figma_discover.py', |
||||
|
'**/__tests__/**', |
||||
|
], |
||||
|
}, |
||||
|
js.configs.recommended, |
||||
|
...tseslint.configs.recommended, |
||||
|
{ |
||||
|
files: ['**/*.{ts,tsx}'], |
||||
|
plugins: { |
||||
|
react, |
||||
|
'react-hooks': reactHooks, |
||||
|
local: { rules: { 'no-hardcoded-hex-color': noHardcodedHexColor } }, |
||||
|
}, |
||||
|
languageOptions: { |
||||
|
parser: tseslint.parser, |
||||
|
parserOptions: { ecmaFeatures: { jsx: true } }, |
||||
|
sourceType: 'module', |
||||
|
globals: { ...globals.node, ...globals.react }, |
||||
|
}, |
||||
|
settings: { react: { version: 'detect' } }, |
||||
|
rules: { |
||||
|
...react.configs.recommended.rules, |
||||
|
...reactHooks.configs.recommended.rules, |
||||
|
'react/react-in-jsx-scope': 'off', |
||||
|
'react/jsx-uses-react': 'off', |
||||
|
'@typescript-eslint/no-unused-vars': [ |
||||
|
'error', |
||||
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, |
||||
|
], |
||||
|
'@typescript-eslint/no-explicit-any': 'warn', |
||||
|
'no-empty': ['error', { allowEmptyCatch: true }], |
||||
|
'local/no-hardcoded-hex-color': 'error', |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
// 项目配置文件为 CommonJS,含 require / module / process
|
||||
|
files: ['eslint.config.js', 'babel.config.js', 'jest.config.js', 'app.config.js', '*.cjs'], |
||||
|
languageOptions: { |
||||
|
sourceType: 'commonjs', |
||||
|
globals: { ...globals.node }, |
||||
|
}, |
||||
|
rules: { |
||||
|
'no-undef': 'off', |
||||
|
'@typescript-eslint/no-require-imports': 'off', |
||||
|
'@typescript-eslint/no-explicit-any': 'off', |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
// 设计令牌文件是颜色唯一来源,豁免 hex 检查
|
||||
|
files: ['**/theme/**/*.ts'], |
||||
|
rules: { 'local/no-hardcoded-hex-color': 'off' }, |
||||
|
}, |
||||
|
); |
||||
@ -0,0 +1,38 @@ |
|||||
|
jest.mock('@react-native-async-storage/async-storage', () => { |
||||
|
const store = new Map<string, string>(); |
||||
|
const impl = { |
||||
|
setItem: jest.fn(async (k: string, v: string) => { |
||||
|
store.set(k, v); |
||||
|
}), |
||||
|
getItem: jest.fn(async (k: string) => (store.has(k) ? (store.get(k) as string) : null)), |
||||
|
removeItem: jest.fn(async (k: string) => { |
||||
|
store.delete(k); |
||||
|
}), |
||||
|
clear: jest.fn(async () => store.clear()), |
||||
|
getAllKeys: jest.fn(async () => Array.from(store.keys())), |
||||
|
}; |
||||
|
return { __esModule: true, default: impl, ...impl }; |
||||
|
}); |
||||
|
|
||||
|
import { storageGet, storageSet, storageRemove } from '../lib/storage'; |
||||
|
|
||||
|
describe('storage 封装(async-storage 轻量持久化)', () => { |
||||
|
beforeEach(async () => { |
||||
|
await storageRemove('ut_token'); |
||||
|
}); |
||||
|
|
||||
|
it('set 后 get 可回读,且键自动加前缀', async () => { |
||||
|
await storageSet('ut_token', 'abc123'); |
||||
|
expect(await storageGet('ut_token')).toBe('abc123'); |
||||
|
}); |
||||
|
|
||||
|
it('remove 后 get 返回 null', async () => { |
||||
|
await storageSet('ut_token', 'xyz'); |
||||
|
await storageRemove('ut_token'); |
||||
|
expect(await storageGet('ut_token')).toBeNull(); |
||||
|
}); |
||||
|
|
||||
|
it('读取不存在的键返回 null', async () => { |
||||
|
expect(await storageGet('ut_missing_key')).toBeNull(); |
||||
|
}); |
||||
|
}); |
||||
Loading…
Reference in new issue