Unify K1 discovery ownership and verification across enrollment paths

This commit is contained in:
DCCONSTRUCTIONS
2026-09-07 20:04:46 +03:00
parent 9c4d70b0e9
commit 762d77ef95
27 changed files with 615 additions and 191 deletions
@@ -16,6 +16,7 @@ let K1ConnectionPipelines;
let physicalRecoveryConnectionDetail;
let shouldRenderK1OperationalPanels;
let K1ProvisioningPipeline;
let K1WifiPasswordField;
let RuntimeActionFenceTestContext;
let LOCAL_OPERATION_PROGRESS_DELAY_MILLISECONDS;
let emptySearchPresentation;
@@ -102,6 +103,9 @@ before(async () => {
logLevel: "silent",
server: { middlewareMode: true },
});
({K1WifiPasswordField} = await server.ssrLoadModule(
"@xgrids-k1/frontend/components/K1WifiPasswordField.tsx",
));
({ normalizeXgridsK1MissionState } = await server.ssrLoadModule(
"@xgrids-k1/frontend/runtimeContext.tsx",
));
@@ -277,7 +281,7 @@ test("K1 one-intent source contract makes mode reset explicit and keeps device I
assert.match(resultRows, /onSelect=\{\(\) => selectCandidate\(device\)\}/);
assert.doesNotMatch(resultRows, /Переподключиться|reopen|verifyConnection/);
assert.match(source, /onChange=\{\(event\) => setSsid\(event\.target\.value\)\}/);
assert.match(source, /onChange=\{\(event\) => setPassword\(event\.target\.value\)\}/);
assert.match(source, /<K1WifiPasswordField[\s\S]*?onChange=\{setPassword\}/);
assert.equal((apply.match(/await connect\(/g) ?? []).length, 1);
assert.doesNotMatch(
@@ -290,9 +294,13 @@ test("K1 one-intent source contract makes mode reset explicit and keeps device I
source,
/(?:color|background(?:-color)?):\s*(?:#[0-9a-f]{3,8}|rgba?\()/i,
);
const passwordSource = readFileSync(new URL(
"../../../plugins/xgrids-k1/frontend/src/components/K1WifiPasswordField.tsx", import.meta.url,
), "utf8");
for (const sharedControl of ["Button", "IconButton", "TextField", "ActivityIndicator", "StatusBadge"]) {
assert.match(source, new RegExp(`<${sharedControl}\\b`), sharedControl);
assert.match(source + passwordSource, new RegExp(`<${sharedControl}\\b`), sharedControl);
}
assert.doesNotMatch(passwordSource, /<(?:button|input)\b/);
assert.equal((source.match(/buttonLabel:\s*"Применить"/g) ?? []).length, 3);
assert.match(source, /устарел|stale/i);
assert.match(source, /network_outcome_unknown|safe_to_retry/i);
@@ -1181,7 +1189,7 @@ function elementByProp(node, propName, expectedValue) {
return elementByProp(node.props.children, propName, expectedValue);
}
function createStatefulProvisioningHarness(initialProps) {
function createStatefulProvisioningHarness(initialProps, Component = K1ProvisioningPipeline) {
const hookSlots = [];
let currentProps = initialProps;
let capturedTree = null;
@@ -1296,7 +1304,7 @@ function createStatefulProvisioningHarness(initialProps) {
};
try {
capturedTree = K1ProvisioningPipeline(currentProps);
capturedTree = Component(currentProps);
} finally {
dispatcher.useState = originals.useState;
dispatcher.useRef = originals.useRef;
@@ -4669,23 +4677,11 @@ test("post-reset Scan selects the exact prior UUID locally and opens network fie
"local selection must not manufacture a backend Apply attempt",
);
const passwordField = elementByProp(tree, "label", "Пароль Wi‑Fi");
const passwordField = elementByProp(tree, "placeholder", "Введите пароль");
assert.ok(passwordField);
assert.equal(passwordField.props.type, "password");
passwordField.props.onChange({ target: { value: "test-only-password" } });
passwordField.props.onChange("test-only-password");
tree = harness.render(props());
harness.flushEffects();
const showPassword = elementByProp(tree, "label", "Показать пароль");
assert.ok(showPassword);
assert.equal(showPassword.props.disabled, false);
showPassword.props.onClick();
tree = harness.render(props());
harness.flushEffects();
assert.equal(
elementByProp(tree, "label", "Пароль Wi‑Fi").props.type,
"text",
);
assert.ok(elementByProp(tree, "label", "Скрыть пароль"));
const chooseAnother = actionByLabel(tree, "Выбрать другое");
assert.ok(chooseAnother);
@@ -7333,7 +7329,7 @@ test("Step-2 credentials and Apply share one canonical field stack", () => {
assert.match(
form,
/:\s*\(\s*<div className="field-stack"[\s\S]*?label="Пароль Wi‑Fi"[\s\S]*?\{applyAction\}\s*<\/div>/,
/:\s*\(\s*<div className="field-stack"[\s\S]*?<K1WifiPasswordField[\s\S]*?\{applyAction\}\s*<\/div>/,
);
assert.match(form, /disabled=\{provisioningFieldsDisabled\}/);
assert.match(form, /value=\{connectionAttemptOwnsDraft \? "" : password\}/);
@@ -7769,3 +7765,26 @@ test("a failed Connect after completed Bluetooth search exposes recovery instead
assert.match(markup, /K1 ответил ошибкой Bluetooth/);
assert.doesNotMatch(markup, /Подключение не выполнено|дождитесь, пока система|private transport exception|Пароль передан/);
});
test("shared K1 password field reveals on demand and hides after clearing", () => {
let value = "";
const props = () => ({value, onChange:next=>{value=next;}, disabled:false});
const harness = createStatefulProvisioningHarness(props(), K1WifiPasswordField);
try {
let tree = harness.render(props());
assert.equal(elementByProp(tree, "label", "Показать пароль").props.disabled, true);
elementByProp(tree, "label", "Пароль Wi‑Fi").props.onChange({target:{value:"test-only-value"}});
tree = harness.render(props()); harness.flushEffects();
elementByProp(tree, "label", "Показать пароль").props.onClick();
tree = harness.render(props());
assert.equal(elementByProp(tree, "label", "Пароль Wi‑Fi").props.type, "text");
assert.equal(elementByProp(tree, "label", "Скрыть пароль").props['aria-pressed'], true);
value = "";
tree = harness.render(props()); harness.flushEffects();
tree = harness.render(props());
assert.equal(elementByProp(tree, "label", "Пароль Wi‑Fi").props.type, "password");
assert.equal(elementByProp(tree, "label", "Показать пароль").props.disabled, true);
assert.equal(elementByProp(tree, "label", "Пароль Wi‑Fi").props.autoComplete, "off");
} finally { harness.dispose(); }
});