build(eslint): focus-safe-disabled folds into button-primitive
Two rules looked at the same <button>: button-primitive refused a raw one outside the control primitives, focus-safe-disabled refused a raw `disabled` one everywhere but FocusSafeButton and AnchoredPopover. They are one rule now, with the same reach: outside the primitives a raw button is refused (a disabled one with the focus message); inside a primitive and in a test only a raw `<button disabled>` is, and the two native-disabled owners are named in the rule rather than in a config override. One override block, one rule entry and two register rows go; the snapshot loses a config group. focusSafe.contract.test.ts pins every case, the primitive and test ones new.
This commit is contained in:
@@ -2,16 +2,19 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
import { eslint } from "./lintProbe.testkit";
|
||||
|
||||
// `local/focus-safe-disabled` (eslint.config.js): a raw `<button disabled>` throws a focused keyboard
|
||||
// user to <body> the moment it turns disabled. ~60 were swept onto ui/FocusSafeButton; this pins that
|
||||
// the rule keeping a new one out actually fires.
|
||||
const RULE = "local/focus-safe-disabled";
|
||||
|
||||
// `local/button-primitive`'s disabled check (eslint.config.js): a raw `<button disabled>` throws a
|
||||
// focused keyboard user to <body> the moment it turns disabled. ~60 were swept onto ui/FocusSafeButton;
|
||||
// this pins that the check keeping a new one out fires — also inside a control primitive and a test,
|
||||
// where the rest of the rule stands aside.
|
||||
async function hits(source: string, filePath = "src/components/__probe.tsx"): Promise<number> {
|
||||
const [result] = await eslint.lintText(source, { filePath });
|
||||
return (result?.messages ?? []).filter((m) => m.ruleId === RULE).length;
|
||||
return (result?.messages ?? []).filter(
|
||||
(m) => m.ruleId === "local/button-primitive" && m.messageId === "disabled",
|
||||
).length;
|
||||
}
|
||||
|
||||
const DISABLED = "export const D = (p: { d: boolean }) => <button disabled={p.d}>x</button>;\n";
|
||||
|
||||
describe("a raw button that can be disabled", () => {
|
||||
it("is refused, statically or not", async () => {
|
||||
expect(
|
||||
@@ -20,13 +23,15 @@ describe("a raw button that can be disabled", () => {
|
||||
expect(await hits("export const B = () => <button disabled>x</button>;\n")).toBe(1);
|
||||
});
|
||||
|
||||
it("leaves a raw button with no disabled, and the primitive, alone", async () => {
|
||||
it("is refused inside a control primitive and in a test too", async () => {
|
||||
expect(await hits(DISABLED, "src/components/ui/Button.tsx")).toBe(1);
|
||||
// an existing test file: the project service admits only lintProbe.testkit's probe names
|
||||
expect(await hits(DISABLED, "src/components/Feed.countStale.dom.test.tsx")).toBe(1);
|
||||
});
|
||||
|
||||
it("leaves a raw button with no disabled, and the two native-disabled owners, alone", async () => {
|
||||
expect(await hits("export const C = () => <button onClick={() => {}}>x</button>;\n")).toBe(0);
|
||||
expect(
|
||||
await hits(
|
||||
"export const D = (p: { d: boolean }) => <button disabled={p.d}>x</button>;\n",
|
||||
"src/components/ui/FocusSafeButton.tsx",
|
||||
),
|
||||
).toBe(0);
|
||||
expect(await hits(DISABLED, "src/components/ui/FocusSafeButton.tsx")).toBe(0);
|
||||
expect(await hits(DISABLED, "src/components/ui/AnchoredPopover.tsx")).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,48 +1,6 @@
|
||||
{
|
||||
"eslint": {
|
||||
"configs": {
|
||||
"FocusSafeButton.tsx + AnchoredPopover.tsx": {
|
||||
"languageOptions": {
|
||||
"ecmaVersion": 2022,
|
||||
"globals": "1169 names, sha1 aee621727a03",
|
||||
"parser": "typescript-eslint/parser",
|
||||
"parserOptions": { "projectService": true, "tsconfigRootDir": "." },
|
||||
"sourceType": "module"
|
||||
},
|
||||
"linterOptions": { "reportUnusedDisableDirectives": 2 },
|
||||
"notApplied": [],
|
||||
"plugins": [
|
||||
"@",
|
||||
"@eslint-community/eslint-comments",
|
||||
"@typescript-eslint",
|
||||
"local",
|
||||
"react-hooks",
|
||||
"react-refresh"
|
||||
],
|
||||
"rules": {
|
||||
"local/focus-safe-disabled": [0],
|
||||
"no-restricted-imports": [
|
||||
2,
|
||||
{
|
||||
"patterns": [
|
||||
{
|
||||
"message": "components/ui/ is domain-free: a platform component may not import a feature component. Invert the dependency (take a prop/render-prop) or keep the component outside ui/.",
|
||||
"regex": "^\\.\\./(?!\\.\\./)"
|
||||
},
|
||||
{
|
||||
"message": "components/ui/ is domain-free: a platform component may not import a feature component. Invert the dependency (take a prop/render-prop) or keep the component outside ui/.",
|
||||
"regex": "^\\.\\./\\.\\./components/(?!ui/)"
|
||||
},
|
||||
{
|
||||
"message": "components/ui/ is domain-free: a platform component may not call a domain API slice. Take the data as props.",
|
||||
"regex": "^\\.\\./\\.\\./lib/api(/|$)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"settings": null
|
||||
},
|
||||
"design-lab": {
|
||||
"languageOptions": {
|
||||
"ecmaVersion": 2022,
|
||||
@@ -75,7 +33,6 @@
|
||||
"linterOptions": { "reportUnusedDisableDirectives": 2 },
|
||||
"notApplied": [
|
||||
"local/button-primitive",
|
||||
"local/focus-safe-disabled",
|
||||
"local/glyph-link",
|
||||
"local/input-primitive",
|
||||
"local/jsx-comment-text",
|
||||
@@ -158,7 +115,6 @@
|
||||
"@typescript-eslint/prefer-namespace-keyword",
|
||||
"@typescript-eslint/triple-slash-reference",
|
||||
"local/button-primitive",
|
||||
"local/focus-safe-disabled",
|
||||
"local/glyph-link",
|
||||
"local/input-primitive",
|
||||
"local/jsx-comment-text",
|
||||
@@ -248,7 +204,6 @@
|
||||
"vitest"
|
||||
],
|
||||
"rules": {
|
||||
"local/button-primitive": [0],
|
||||
"local/glyph-link": [0],
|
||||
"local/input-primitive": [0],
|
||||
"local/literal-a11y-text": [0],
|
||||
@@ -711,7 +666,6 @@
|
||||
"linebreak-style": [0],
|
||||
"lines-around-comment": [0],
|
||||
"local/button-primitive": [2],
|
||||
"local/focus-safe-disabled": [2],
|
||||
"local/glyph-link": [2],
|
||||
"local/input-primitive": [2],
|
||||
"local/jsx-comment-text": [2],
|
||||
@@ -1062,7 +1016,6 @@
|
||||
"vitest"
|
||||
],
|
||||
"rules": {
|
||||
"local/button-primitive": [0],
|
||||
"local/glyph-link": [0],
|
||||
"local/input-primitive": [0],
|
||||
"local/literal-a11y-text": [0],
|
||||
@@ -1153,7 +1106,6 @@
|
||||
"linterOptions": { "reportUnusedDisableDirectives": 2 },
|
||||
"notApplied": [
|
||||
"local/button-primitive",
|
||||
"local/focus-safe-disabled",
|
||||
"local/glyph-link",
|
||||
"local/input-primitive",
|
||||
"local/jsx-comment-text",
|
||||
@@ -1262,7 +1214,6 @@
|
||||
"vitest"
|
||||
],
|
||||
"rules": {
|
||||
"local/button-primitive": [0],
|
||||
"local/glyph-link": [0],
|
||||
"local/input-primitive": [0],
|
||||
"local/literal-a11y-text": [0],
|
||||
@@ -1331,10 +1282,6 @@
|
||||
},
|
||||
"files": {
|
||||
"(not linted)": [],
|
||||
"FocusSafeButton.tsx + AnchoredPopover.tsx": [
|
||||
"src/components/ui/AnchoredPopover.tsx",
|
||||
"src/components/ui/FocusSafeButton.tsx"
|
||||
],
|
||||
"design-lab": [
|
||||
"src/design-lab/DesignLab.tsx",
|
||||
"src/design-lab/batchA.tsx",
|
||||
@@ -1959,6 +1906,7 @@
|
||||
],
|
||||
"ui": [
|
||||
"src/components/ui/ActionMenu.tsx",
|
||||
"src/components/ui/AnchoredPopover.tsx",
|
||||
"src/components/ui/Avatar.tsx",
|
||||
"src/components/ui/BackToTop.tsx",
|
||||
"src/components/ui/Badge.tsx",
|
||||
@@ -1978,6 +1926,7 @@
|
||||
"src/components/ui/FilterChipGroup.tsx",
|
||||
"src/components/ui/FilterFold.tsx",
|
||||
"src/components/ui/FilterSelect.tsx",
|
||||
"src/components/ui/FocusSafeButton.tsx",
|
||||
"src/components/ui/Headings.tsx",
|
||||
"src/components/ui/HiddenFilterCue.tsx",
|
||||
"src/components/ui/IconButton.tsx",
|
||||
|
||||
Reference in New Issue
Block a user