milestone 29: activity — history, live and policy simulation on one surface
Gates / test (push) Successful in 1m40s
Gates / package (push) Successful in 3m58s
Gates / container (push) Successful in 14s
CI / gates (push) Successful in 12m51s
Gates / frontend (push) Successful in 1m18s
Gates / test-aarch64 (push) Successful in 6m57s
Gates / test (push) Successful in 1m40s
Gates / package (push) Successful in 3m58s
Gates / container (push) Successful in 14s
CI / gates (push) Successful in 12m51s
Gates / frontend (push) Successful in 1m18s
Gates / test-aarch64 (push) Successful in 6m57s
query log, live and lookup merge into /activity. history filters live in the url, so a pasted link or back/forward reproduces the exact view; the result column separates servfail and nxdomain from success in the list. live is follow-by-default with freeze, and a streamed row opens its in-memory provenance detail — no correlation invented for rows sqlite has not written. lookup survives as the current policy simulation under /activity/test. investigation links carry absolute bounds, and the diagnostics page now honors since/until instead of ignoring them. the old routes are gone without aliases.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import Select from "./Select";
|
||||
|
||||
const OPTIONS = [
|
||||
{ value: "any", label: "All" },
|
||||
{ value: "blocked", label: "Blocked only" },
|
||||
];
|
||||
|
||||
/** RAC opens a Select from the keyboard as readily as from a pointer. */
|
||||
function open(trigger: HTMLElement) {
|
||||
fireEvent.keyDown(trigger, { key: "Enter" });
|
||||
fireEvent.keyUp(trigger, { key: "Enter" });
|
||||
}
|
||||
|
||||
test("a disabled select keeps its value on screen but takes no input", () => {
|
||||
const onChange = vi.fn();
|
||||
render(<Select label="Status" options={OPTIONS} value="blocked" onChange={onChange} isDisabled />);
|
||||
|
||||
const trigger = screen.getByRole("button");
|
||||
expect(trigger.textContent).toContain("Blocked only");
|
||||
// A disabled button is out of the tab order by definition, so the filter row
|
||||
// cannot be reached by keyboard while live mode owns it.
|
||||
expect(trigger).toHaveProperty("disabled", true);
|
||||
|
||||
open(trigger);
|
||||
expect(screen.queryByRole("listbox")).toBeNull();
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("an enabled select still opens", () => {
|
||||
render(<Select label="Status" options={OPTIONS} value="any" onChange={vi.fn()} />);
|
||||
open(screen.getByRole("button"));
|
||||
expect(screen.getByRole("listbox")).toBeTruthy();
|
||||
});
|
||||
+16
-2
@@ -32,6 +32,8 @@ interface Props {
|
||||
* dialog uses, `inline` a control sitting in a row of other controls.
|
||||
*/
|
||||
variant?: "field" | "compactField" | "inline";
|
||||
/** Visible but inert, keeping its value on screen; RAC also drops it from the tab order. */
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
const styles = stylex.create({
|
||||
@@ -50,7 +52,10 @@ const styles = stylex.create({
|
||||
justifyContent: "space-between",
|
||||
gap: "0.5rem",
|
||||
textAlign: "left",
|
||||
cursor: "pointer",
|
||||
// RAC renders a real `<button disabled>`, so the state is reachable as a
|
||||
// pseudo-class rather than needing a second style object.
|
||||
cursor: { default: "pointer", ":disabled": "not-allowed" },
|
||||
opacity: { default: null, ":disabled": 0.55 },
|
||||
},
|
||||
compact: {
|
||||
marginTop: "0.25rem",
|
||||
@@ -105,12 +110,21 @@ const styles = stylex.create({
|
||||
},
|
||||
});
|
||||
|
||||
export default function Select({ options, value, onChange, label, "aria-label": ariaLabel, variant = "field" }: Props) {
|
||||
export default function Select({
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
label,
|
||||
"aria-label": ariaLabel,
|
||||
variant = "field",
|
||||
isDisabled = false,
|
||||
}: Props) {
|
||||
const base = variant === "field" ? shared.input : shared.smallInput;
|
||||
const block = variant === "compactField" ? styles.compact : null;
|
||||
return (
|
||||
<AriaSelect
|
||||
aria-label={ariaLabel}
|
||||
isDisabled={isDisabled}
|
||||
value={value}
|
||||
onChange={(key) => onChange(String(key ?? ""))}
|
||||
{...stylex.props(styles.root)}
|
||||
|
||||
Reference in New Issue
Block a user