mirror of
https://github.com/mgeeky/decode-spam-headers.git
synced 2026-02-22 05:23:31 +01:00
MAESTRO: Make decode-all toggle stateful
This commit is contained in:
@@ -35,7 +35,7 @@ This phase implements the test selection panel and analysis configuration contro
|
|||||||
- [x] Select All / Deselect All buttons work correctly
|
- [x] Select All / Deselect All buttons work correctly
|
||||||
- [x] Search/filter narrows visible tests by name
|
- [x] Search/filter narrows visible tests by name
|
||||||
- [x] DNS resolution toggle defaults to off
|
- [x] DNS resolution toggle defaults to off
|
||||||
- [ ] Decode-all toggle is functional
|
- [x] Decode-all toggle is functional
|
||||||
- [ ] All controls are keyboard accessible (Tab, Enter, Space)
|
- [ ] All controls are keyboard accessible (Tab, Enter, Space)
|
||||||
- [ ] Linting passes (`ruff check backend/`, `npx eslint src/`)
|
- [ ] Linting passes (`ruff check backend/`, `npx eslint src/`)
|
||||||
- [ ] Run `/speckit.analyze` to verify consistency
|
- [ ] Run `/speckit.analyze` to verify consistency
|
||||||
|
|||||||
@@ -110,6 +110,27 @@ describe("AnalysisControls", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("updates toggles without a controlled config", async () => {
|
||||||
|
setupFetchMock(sampleTests);
|
||||||
|
const handleChange = vi.fn();
|
||||||
|
|
||||||
|
const { container } = render(<AnalysisControls onChange={handleChange} />);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await flushPromises();
|
||||||
|
});
|
||||||
|
|
||||||
|
const decodeToggle = getToggle(container, "toggle-decode-all");
|
||||||
|
act(() => {
|
||||||
|
decodeToggle.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(decodeToggle.getAttribute("aria-checked")).toBe("true");
|
||||||
|
expect(handleChange).toHaveBeenLastCalledWith(
|
||||||
|
expect.objectContaining({ decodeAll: true }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("updates toggles on click and keyboard", async () => {
|
it("updates toggles on click and keyboard", async () => {
|
||||||
setupFetchMock(sampleTests);
|
setupFetchMock(sampleTests);
|
||||||
const handleChange = vi.fn();
|
const handleChange = vi.fn();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import type { KeyboardEvent } from "react";
|
import { useState, type KeyboardEvent } from "react";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import {
|
import {
|
||||||
faToggleOff,
|
faToggleOff,
|
||||||
@@ -34,19 +34,29 @@ const handleToggleKeyDown = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function AnalysisControls({
|
export default function AnalysisControls({
|
||||||
config = defaultConfig,
|
config,
|
||||||
onChange,
|
onChange,
|
||||||
}: AnalysisControlsProps) {
|
}: AnalysisControlsProps) {
|
||||||
|
const [internalConfig, setInternalConfig] = useState<AnalysisConfig>(defaultConfig);
|
||||||
|
const resolvedConfig = config ?? internalConfig;
|
||||||
|
|
||||||
|
const commitConfig = (nextConfig: AnalysisConfig) => {
|
||||||
|
if (!config) {
|
||||||
|
setInternalConfig(nextConfig);
|
||||||
|
}
|
||||||
|
onChange(nextConfig);
|
||||||
|
};
|
||||||
|
|
||||||
const updateTests = (nextTestIds: number[]) => {
|
const updateTests = (nextTestIds: number[]) => {
|
||||||
onChange({ ...config, testIds: nextTestIds });
|
commitConfig({ ...resolvedConfig, testIds: nextTestIds });
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleResolve = () => {
|
const toggleResolve = () => {
|
||||||
onChange({ ...config, resolve: !config.resolve });
|
commitConfig({ ...resolvedConfig, resolve: !resolvedConfig.resolve });
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleDecodeAll = () => {
|
const toggleDecodeAll = () => {
|
||||||
onChange({ ...config, decodeAll: !config.decodeAll });
|
commitConfig({ ...resolvedConfig, decodeAll: !resolvedConfig.decodeAll });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -70,15 +80,15 @@ export default function AnalysisControls({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={config.resolve}
|
aria-checked={resolvedConfig.resolve}
|
||||||
aria-label="Toggle DNS resolution"
|
aria-label="Toggle DNS resolution"
|
||||||
data-testid="toggle-resolve"
|
data-testid="toggle-resolve"
|
||||||
className="inline-flex items-center gap-2 rounded-full border border-info/20 bg-background/60 px-3 py-2 text-xs text-text/70 transition hover:border-info/40 hover:text-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info"
|
className="inline-flex items-center gap-2 rounded-full border border-info/20 bg-background/60 px-3 py-2 text-xs text-text/70 transition hover:border-info/40 hover:text-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info"
|
||||||
onClick={toggleResolve}
|
onClick={toggleResolve}
|
||||||
onKeyDown={(event) => handleToggleKeyDown(event, toggleResolve)}
|
onKeyDown={(event) => handleToggleKeyDown(event, toggleResolve)}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={config.resolve ? faToggleOn : faToggleOff} />
|
<FontAwesomeIcon icon={resolvedConfig.resolve ? faToggleOn : faToggleOff} />
|
||||||
{config.resolve ? "On" : "Off"}
|
{resolvedConfig.resolve ? "On" : "Off"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between gap-4 rounded-xl border border-info/10 bg-background/40 p-4">
|
<div className="flex items-center justify-between gap-4 rounded-xl border border-info/10 bg-background/40 p-4">
|
||||||
@@ -94,20 +104,20 @@ export default function AnalysisControls({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="switch"
|
role="switch"
|
||||||
aria-checked={config.decodeAll}
|
aria-checked={resolvedConfig.decodeAll}
|
||||||
aria-label="Toggle decode all"
|
aria-label="Toggle decode all"
|
||||||
data-testid="toggle-decode-all"
|
data-testid="toggle-decode-all"
|
||||||
className="inline-flex items-center gap-2 rounded-full border border-info/20 bg-background/60 px-3 py-2 text-xs text-text/70 transition hover:border-info/40 hover:text-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info"
|
className="inline-flex items-center gap-2 rounded-full border border-info/20 bg-background/60 px-3 py-2 text-xs text-text/70 transition hover:border-info/40 hover:text-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info"
|
||||||
onClick={toggleDecodeAll}
|
onClick={toggleDecodeAll}
|
||||||
onKeyDown={(event) => handleToggleKeyDown(event, toggleDecodeAll)}
|
onKeyDown={(event) => handleToggleKeyDown(event, toggleDecodeAll)}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={config.decodeAll ? faToggleOn : faToggleOff} />
|
<FontAwesomeIcon icon={resolvedConfig.decodeAll ? faToggleOn : faToggleOff} />
|
||||||
{config.decodeAll ? "On" : "Off"}
|
{resolvedConfig.decodeAll ? "On" : "Off"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<TestSelector selectedTestIds={config.testIds} onSelectionChange={updateTests} />
|
<TestSelector selectedTestIds={resolvedConfig.testIds} onSelectionChange={updateTests} />
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user