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