Unify K1 discovery ownership and verification across enrollment paths
This commit is contained in:
@@ -260,7 +260,8 @@ test("K1 connection surface enforces one scan, local selection, and one Apply",
|
||||
assert.doesNotMatch(provisioning, /allow_host_wifi_switch/);
|
||||
assert.doesNotMatch(provisioning, /(?:color|background(?:-color)?):\s*(?:#[0-9a-f]{3,8}|rgba?\()/i);
|
||||
for (const sharedControl of ["Button", "IconButton", "TextField", "ActivityIndicator", "StatusBadge"]) {
|
||||
assert.match(provisioning, new RegExp(`<${sharedControl}\\b`), sharedControl);
|
||||
const password = readFileSync(join(pluginFrontendRoot, "components/K1WifiPasswordField.tsx"), "utf8");
|
||||
assert.match(provisioning + password, new RegExp(`<${sharedControl}\\b`), sharedControl);
|
||||
}
|
||||
assert.doesNotMatch(
|
||||
provisioning,
|
||||
@@ -736,7 +737,7 @@ test("K1 provisioning keeps the operator draft separate from the backend lease",
|
||||
assert.match(provisioning, /scanWithResult\(\{ durationSeconds: BLE_DISCOVERY_TIMEOUT_SECONDS \}\)/);
|
||||
assert.match(
|
||||
provisioning,
|
||||
/onChange=\{\(event\) => setPassword\(event\.target\.value\)\}/,
|
||||
/<K1WifiPasswordField[\s\S]*?onChange=\{setPassword\}/,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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(); }
|
||||
});
|
||||
|
||||
@@ -204,3 +204,31 @@ test('pending enrollment is presented by its action button without a second noti
|
||||
}}),'');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test('discovery errors preserve their cause and never imply a Wi-Fi refusal',()=>{
|
||||
for(const [code,text,requiresScan] of [
|
||||
['ble-discovery-busy',/поиск уже выполняется/i,false],
|
||||
['ble-adapter-unavailable',/Bluetooth недоступен/,false],
|
||||
['ble-discovery-failed',/Служба Bluetooth/,false],
|
||||
['ble-selected-device-unavailable',/Выбранный K1 не найден/,true],
|
||||
]){
|
||||
const state={...initial,connection_attempt:{schema_version:'missioncore.xgrids-k1-connection-attempt/v1',
|
||||
attempt_id:'test',status:'failed',stage:'resolution-failed',phase:'network_not_applied',
|
||||
public_error_code:code,side_effect_status:'none'}};
|
||||
assert.match(api.enrollmentNotice(state),text);
|
||||
assert.match(api.enrollmentNotice(state),/Настройки Wi-Fi не были отправлены/);
|
||||
assert.doesNotMatch(api.enrollmentNotice(state),/Проверьте.*пароль/);
|
||||
assert.equal(api.enrollmentBluetoothFailure(state),requiresScan);
|
||||
state.connection_attempt.side_effect_status='unknown';
|
||||
state.connection_attempt.phase='network_outcome_unknown';
|
||||
assert.doesNotMatch(api.enrollmentNotice(state),/не были отправлены/);
|
||||
}
|
||||
});
|
||||
|
||||
test('legacy resolution failure invalidates the form before a second stale submission',()=>{
|
||||
const state={...initial,connection_attempt:{schema_version:'missioncore.xgrids-k1-connection-attempt/v1',
|
||||
attempt_id:'test',status:'failed',stage:'resolution-failed',public_error_code:'BleakDBusError',side_effect_status:'none'}};
|
||||
assert.equal(api.enrollmentBluetoothFailure(state),true);
|
||||
assert.match(api.enrollmentNotice(state),/Найдите K1 ещё раз/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user