@redrockswebdevelopment/formstack-form-testing 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +105 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +270 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3385,7 +3385,7 @@ export function createLiveFormSampleFillPlan(definition, sampleValues, options =
|
|
|
3385
3385
|
const entry = {
|
|
3386
3386
|
key,
|
|
3387
3387
|
target,
|
|
3388
|
-
value: samples[key],
|
|
3388
|
+
value: normalizeLiveFormSampleValue(config.kind, samples[key]),
|
|
3389
3389
|
...(config.kind === undefined ? {} : { fieldKind: config.kind }),
|
|
3390
3390
|
...(config.required === undefined ? {} : { required: config.required }),
|
|
3391
3391
|
};
|
|
@@ -3431,6 +3431,44 @@ function createSampleSmokeReadinessOptions(definition, options, defaultEnabled)
|
|
|
3431
3431
|
}
|
|
3432
3432
|
return createLiveFormReadinessOptions(definition, { ...options, requiredOnly: true });
|
|
3433
3433
|
}
|
|
3434
|
+
function stableSerializeLiveFormValue(value) {
|
|
3435
|
+
if (value === undefined)
|
|
3436
|
+
return "undefined";
|
|
3437
|
+
const normalize = (entry) => {
|
|
3438
|
+
if (Array.isArray(entry))
|
|
3439
|
+
return entry.map(normalize);
|
|
3440
|
+
if (entry && typeof entry === "object") {
|
|
3441
|
+
return Object.fromEntries(Object.entries(entry).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, normalize(child)]));
|
|
3442
|
+
}
|
|
3443
|
+
return entry;
|
|
3444
|
+
};
|
|
3445
|
+
return JSON.stringify(normalize(value));
|
|
3446
|
+
}
|
|
3447
|
+
export function normalizeLiveFormSampleValue(kind = "unknown", value) {
|
|
3448
|
+
if (value instanceof Date)
|
|
3449
|
+
return value.toISOString().slice(0, 10);
|
|
3450
|
+
if (["radio", "rating", "select"].includes(kind) && typeof value === "string") {
|
|
3451
|
+
return { value };
|
|
3452
|
+
}
|
|
3453
|
+
if (kind === "checkbox" && Array.isArray(value)) {
|
|
3454
|
+
return buildLiveFormCheckboxValue(value.filter((entry) => typeof entry === "string"));
|
|
3455
|
+
}
|
|
3456
|
+
if (kind === "checkbox" && value && typeof value === "object" && Array.isArray(value.values)) {
|
|
3457
|
+
return buildLiveFormCheckboxValue(value.values.filter((entry) => typeof entry === "string"));
|
|
3458
|
+
}
|
|
3459
|
+
if (kind === "checkbox" && typeof value === "boolean") {
|
|
3460
|
+
return { checked: value };
|
|
3461
|
+
}
|
|
3462
|
+
return value;
|
|
3463
|
+
}
|
|
3464
|
+
export function createLiveFormFieldValueAdapter(kind = "unknown") {
|
|
3465
|
+
return {
|
|
3466
|
+
kind,
|
|
3467
|
+
normalize: (value) => normalizeLiveFormSampleValue(kind, value),
|
|
3468
|
+
isEmpty: (value) => isLiveFormFieldValueEmpty(normalizeLiveFormSampleValue(kind, value)),
|
|
3469
|
+
equals: (actual, expected) => stableSerializeLiveFormValue(normalizeLiveFormSampleValue(kind, actual)) === stableSerializeLiveFormValue(normalizeLiveFormSampleValue(kind, expected)),
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3434
3472
|
export async function runLiveFormSampleSmoke(page, definition, sampleValues, options = {}) {
|
|
3435
3473
|
const beforeReadinessOptions = createSampleSmokeReadinessOptions(definition, options.beforeReadiness, false);
|
|
3436
3474
|
const afterReadinessOptions = createSampleSmokeReadinessOptions(definition, options.afterReadiness, true);
|
|
@@ -3456,8 +3494,10 @@ export async function runLiveFormSampleSmoke(page, definition, sampleValues, opt
|
|
|
3456
3494
|
};
|
|
3457
3495
|
}
|
|
3458
3496
|
function sampleValueShapeIssue(key, kind, value) {
|
|
3497
|
+
const normalizedValue = normalizeLiveFormSampleValue(kind, value);
|
|
3459
3498
|
const objectValue = value && typeof value === "object" ? value : null;
|
|
3460
|
-
|
|
3499
|
+
const normalizedObjectValue = normalizedValue && typeof normalizedValue === "object" ? normalizedValue : null;
|
|
3500
|
+
if (["radio", "select", "rating"].includes(kind ?? "") && (normalizedObjectValue === null || typeof normalizedObjectValue.value !== "string")) {
|
|
3461
3501
|
return {
|
|
3462
3502
|
kind: "invalid-value-shape",
|
|
3463
3503
|
key,
|
|
@@ -3465,7 +3505,7 @@ function sampleValueShapeIssue(key, kind, value) {
|
|
|
3465
3505
|
detail: { kind },
|
|
3466
3506
|
};
|
|
3467
3507
|
}
|
|
3468
|
-
if (["checkbox", "matrix"].includes(kind ?? "") &&
|
|
3508
|
+
if (["checkbox", "matrix"].includes(kind ?? "") && normalizedObjectValue === null) {
|
|
3469
3509
|
return {
|
|
3470
3510
|
kind: "invalid-value-shape",
|
|
3471
3511
|
key,
|
|
@@ -3523,7 +3563,7 @@ export function validateLiveFormSampleProfile(definition, sampleValues, options
|
|
|
3523
3563
|
continue;
|
|
3524
3564
|
if (options.requiredOnly === true && config.required !== true)
|
|
3525
3565
|
continue;
|
|
3526
|
-
const value = samples[key];
|
|
3566
|
+
const value = normalizeLiveFormSampleValue(config.kind, samples[key]);
|
|
3527
3567
|
const shapeIssue = sampleValueShapeIssue(key, config.kind, value);
|
|
3528
3568
|
if (shapeIssue)
|
|
3529
3569
|
issues.push({ ...shapeIssue, target });
|
|
@@ -3578,6 +3618,8 @@ export function createLiveFormProgressiveSamplePlan(definition, sampleValues, op
|
|
|
3578
3618
|
...(step?.expectHiddenAfter === undefined ? {} : { expectHiddenAfter: step.expectHiddenAfter }),
|
|
3579
3619
|
...(step?.expectResetAfter === undefined ? {} : { expectResetAfter: step.expectResetAfter }),
|
|
3580
3620
|
...(step?.continueSelector === undefined ? {} : { continueSelector: step.continueSelector }),
|
|
3621
|
+
...(step?.expectVisibleAfterContinue === undefined ? {} : { expectVisibleAfterContinue: step.expectVisibleAfterContinue }),
|
|
3622
|
+
...(step?.expectHiddenAfterContinue === undefined ? {} : { expectHiddenAfterContinue: step.expectHiddenAfterContinue }),
|
|
3581
3623
|
...(step?.transaction === undefined ? {} : { transaction: step.transaction }),
|
|
3582
3624
|
});
|
|
3583
3625
|
}
|
|
@@ -3598,7 +3640,22 @@ export async function runLiveFormProgressiveSampleFlow(page, definition, sampleV
|
|
|
3598
3640
|
...(step.expectResetAfter === undefined ? {} : { expectReset: step.expectResetAfter }),
|
|
3599
3641
|
}));
|
|
3600
3642
|
if (step.continueSelector) {
|
|
3601
|
-
|
|
3643
|
+
try {
|
|
3644
|
+
await page.locator(step.continueSelector).first().click({ timeout: 5_000 });
|
|
3645
|
+
}
|
|
3646
|
+
catch (error) {
|
|
3647
|
+
throw new Error(`Progressive step "${step.key}" could not click continue selector "${step.continueSelector}". The control may still be disabled because the preceding field value did not trigger Formstack side effects. Cause: ${error.message}`);
|
|
3648
|
+
}
|
|
3649
|
+
const continueVisibilityTargets = [
|
|
3650
|
+
...(step.expectVisibleAfterContinue ?? []).map((target) => ({ ...target, visible: true })),
|
|
3651
|
+
...(step.expectHiddenAfterContinue ?? []).map((target) => ({ ...target, visible: false })),
|
|
3652
|
+
];
|
|
3653
|
+
if (continueVisibilityTargets.length > 0) {
|
|
3654
|
+
const visibility = await waitForLiveFormVisibility(page, { targets: continueVisibilityTargets });
|
|
3655
|
+
if (!visibility.matched) {
|
|
3656
|
+
throw new Error(`Progressive step "${step.key}" continue action did not settle into the requested visibility state.`);
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3602
3659
|
}
|
|
3603
3660
|
}
|
|
3604
3661
|
const quietOptions = options.afterQuiet === false ? undefined : options.afterQuiet === true ? {} : options.afterQuiet;
|
|
@@ -3661,10 +3718,14 @@ export async function diagnoseLiveFormSubmitGate(page, options = {}) {
|
|
|
3661
3718
|
blockingReasons.push(`readiness-blocking-fields:${readiness.blockingFieldCount}`);
|
|
3662
3719
|
if (validation && validation.inlineErrors.length > 0)
|
|
3663
3720
|
blockingReasons.push(`validation-errors:${validation.inlineErrors.length}`);
|
|
3721
|
+
const blockingFields = readiness?.fields.filter((field) => field.isBlocking) ?? [];
|
|
3722
|
+
const validationErrors = validation?.inlineErrors ?? [];
|
|
3664
3723
|
return {
|
|
3665
3724
|
selector,
|
|
3666
3725
|
...dom,
|
|
3667
3726
|
blockingReasons,
|
|
3727
|
+
blockingFields,
|
|
3728
|
+
validationErrors,
|
|
3668
3729
|
...(readiness ? { readiness } : {}),
|
|
3669
3730
|
...(validation ? { validation } : {}),
|
|
3670
3731
|
ready: blockingReasons.length === 0,
|
|
@@ -3737,6 +3798,70 @@ export async function diagnoseLiveFormLayout(page, options = {}) {
|
|
|
3737
3798
|
};
|
|
3738
3799
|
}, options);
|
|
3739
3800
|
}
|
|
3801
|
+
export async function diagnoseLiveFormIframeLayout(page, options) {
|
|
3802
|
+
if (!hasEvaluate(page)) {
|
|
3803
|
+
throw new Error("diagnoseLiveFormIframeLayout requires a Playwright page with evaluate().");
|
|
3804
|
+
}
|
|
3805
|
+
return page.evaluate((layoutOptions) => {
|
|
3806
|
+
const iframe = document.querySelector(layoutOptions.iframeSelector);
|
|
3807
|
+
const viewportWidth = document.documentElement.clientWidth;
|
|
3808
|
+
const viewportHeight = document.documentElement.clientHeight;
|
|
3809
|
+
const issues = [];
|
|
3810
|
+
if (!(iframe instanceof HTMLElement)) {
|
|
3811
|
+
return {
|
|
3812
|
+
iframeSelector: layoutOptions.iframeSelector,
|
|
3813
|
+
exists: false,
|
|
3814
|
+
viewportWidth,
|
|
3815
|
+
viewportHeight,
|
|
3816
|
+
rect: null,
|
|
3817
|
+
issues: [{
|
|
3818
|
+
kind: "horizontal-overflow",
|
|
3819
|
+
selector: layoutOptions.iframeSelector,
|
|
3820
|
+
message: "Iframe element was not found.",
|
|
3821
|
+
}],
|
|
3822
|
+
ok: false,
|
|
3823
|
+
};
|
|
3824
|
+
}
|
|
3825
|
+
const rect = iframe.getBoundingClientRect();
|
|
3826
|
+
if (rect.left < -1 || rect.right > viewportWidth + 1) {
|
|
3827
|
+
issues.push({
|
|
3828
|
+
kind: "horizontal-overflow",
|
|
3829
|
+
selector: layoutOptions.iframeSelector,
|
|
3830
|
+
message: "Iframe extends outside the viewport horizontally.",
|
|
3831
|
+
detail: { left: Math.round(rect.left), right: Math.round(rect.right), viewportWidth },
|
|
3832
|
+
});
|
|
3833
|
+
}
|
|
3834
|
+
if (layoutOptions.expectedMaxWidth !== undefined && rect.width > layoutOptions.expectedMaxWidth + 1) {
|
|
3835
|
+
issues.push({
|
|
3836
|
+
kind: "horizontal-overflow",
|
|
3837
|
+
selector: layoutOptions.iframeSelector,
|
|
3838
|
+
message: `Iframe width ${Math.round(rect.width)}px exceeds expected maximum ${layoutOptions.expectedMaxWidth}px.`,
|
|
3839
|
+
detail: { width: Math.round(rect.width), expectedMaxWidth: layoutOptions.expectedMaxWidth },
|
|
3840
|
+
});
|
|
3841
|
+
}
|
|
3842
|
+
if (layoutOptions.expectedMaxHeight !== undefined && rect.height > layoutOptions.expectedMaxHeight + 1) {
|
|
3843
|
+
issues.push({
|
|
3844
|
+
kind: "nested-scroll",
|
|
3845
|
+
selector: layoutOptions.iframeSelector,
|
|
3846
|
+
message: `Iframe height ${Math.round(rect.height)}px exceeds expected maximum ${layoutOptions.expectedMaxHeight}px.`,
|
|
3847
|
+
detail: { height: Math.round(rect.height), expectedMaxHeight: layoutOptions.expectedMaxHeight },
|
|
3848
|
+
});
|
|
3849
|
+
}
|
|
3850
|
+
return {
|
|
3851
|
+
iframeSelector: layoutOptions.iframeSelector,
|
|
3852
|
+
exists: true,
|
|
3853
|
+
viewportWidth,
|
|
3854
|
+
viewportHeight,
|
|
3855
|
+
rect: { width: Math.round(rect.width), height: Math.round(rect.height), top: Math.round(rect.top), left: Math.round(rect.left) },
|
|
3856
|
+
issues,
|
|
3857
|
+
ok: issues.length === 0,
|
|
3858
|
+
};
|
|
3859
|
+
}, options);
|
|
3860
|
+
}
|
|
3861
|
+
function endCascadePhase(name, startedAt, ok) {
|
|
3862
|
+
const endedAt = Date.now();
|
|
3863
|
+
return { name, startedAt, endedAt, durationMs: endedAt - startedAt, ok };
|
|
3864
|
+
}
|
|
3740
3865
|
export async function assertLiveFormChangeCascade(page, options) {
|
|
3741
3866
|
const transaction = await performLiveFormFieldTransaction(page, {
|
|
3742
3867
|
...(options.trigger.transaction ?? {}),
|
|
@@ -3766,6 +3891,136 @@ export async function assertLiveFormChangeCascade(page, options) {
|
|
|
3766
3891
|
}
|
|
3767
3892
|
return { transaction, values, settled };
|
|
3768
3893
|
}
|
|
3894
|
+
export async function traceLiveFormChangeCascade(page, options) {
|
|
3895
|
+
const totalStartedAt = Date.now();
|
|
3896
|
+
const phases = [];
|
|
3897
|
+
const writeStartedAt = Date.now();
|
|
3898
|
+
const transaction = await performLiveFormFieldTransaction(page, {
|
|
3899
|
+
...(options.trigger.transaction ?? {}),
|
|
3900
|
+
write: {
|
|
3901
|
+
...(options.trigger.write ?? {}),
|
|
3902
|
+
target: options.trigger.target,
|
|
3903
|
+
value: options.trigger.value,
|
|
3904
|
+
notify: options.trigger.write?.notify ?? true,
|
|
3905
|
+
},
|
|
3906
|
+
...(options.trigger.fieldKind === undefined ? {} : { fieldKind: options.trigger.fieldKind }),
|
|
3907
|
+
});
|
|
3908
|
+
phases.push(endCascadePhase("write", writeStartedAt, transaction.write.valueMatched !== false));
|
|
3909
|
+
const values = [];
|
|
3910
|
+
const valuesStartedAt = Date.now();
|
|
3911
|
+
for (const expected of options.expectValues ?? []) {
|
|
3912
|
+
values.push(await waitForLiveFormFieldValue(page, expected));
|
|
3913
|
+
}
|
|
3914
|
+
phases.push(endCascadePhase("values", valuesStartedAt, values.every((value) => value.valueMatched !== false)));
|
|
3915
|
+
const visibilityTargets = [
|
|
3916
|
+
...(options.expectVisible ?? []).map((target) => ({ ...target, visible: true })),
|
|
3917
|
+
...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
|
|
3918
|
+
];
|
|
3919
|
+
const settleStartedAt = Date.now();
|
|
3920
|
+
const settled = await waitForLiveFormLogicSettled(page, {
|
|
3921
|
+
quiet: options.quiet ?? false,
|
|
3922
|
+
...(visibilityTargets.length === 0 ? {} : { visibility: { targets: visibilityTargets } }),
|
|
3923
|
+
...(options.validation === undefined ? {} : { validation: options.validation }),
|
|
3924
|
+
});
|
|
3925
|
+
phases.push(endCascadePhase("settled", settleStartedAt, settled.settled));
|
|
3926
|
+
if (!settled.settled || values.some((value) => value.valueMatched === false)) {
|
|
3927
|
+
throw new Error("Expected live form change cascade did not settle into the requested state.");
|
|
3928
|
+
}
|
|
3929
|
+
const endedAt = Date.now();
|
|
3930
|
+
return {
|
|
3931
|
+
label: options.label ?? "live-form-change-cascade",
|
|
3932
|
+
transaction,
|
|
3933
|
+
values,
|
|
3934
|
+
settled,
|
|
3935
|
+
phases,
|
|
3936
|
+
totalMs: endedAt - totalStartedAt,
|
|
3937
|
+
};
|
|
3938
|
+
}
|
|
3939
|
+
export async function runLiveFormRapidFieldChangeSequence(page, options) {
|
|
3940
|
+
const transactions = [];
|
|
3941
|
+
for (const write of options.writes) {
|
|
3942
|
+
transactions.push(await performLiveFormFieldTransaction(page, {
|
|
3943
|
+
...(write.transaction ?? {}),
|
|
3944
|
+
write: {
|
|
3945
|
+
...(write.write ?? {}),
|
|
3946
|
+
target: write.target,
|
|
3947
|
+
value: write.value,
|
|
3948
|
+
notify: write.write?.notify ?? true,
|
|
3949
|
+
},
|
|
3950
|
+
...(write.fieldKind === undefined ? {} : { fieldKind: write.fieldKind }),
|
|
3951
|
+
}));
|
|
3952
|
+
}
|
|
3953
|
+
const quietOptions = options.afterQuiet === false ? undefined : options.afterQuiet === true ? {} : options.afterQuiet;
|
|
3954
|
+
const validationOptions = options.afterValidation === false ? undefined : options.afterValidation === true ? { expectNoErrors: true } : options.afterValidation;
|
|
3955
|
+
const quiet = quietOptions ? await waitForLiveFormQuiet(page, quietOptions) : undefined;
|
|
3956
|
+
const validation = validationOptions ? await waitForLiveFormValidationSettled(page, validationOptions) : undefined;
|
|
3957
|
+
const finalByTarget = new Map();
|
|
3958
|
+
for (const write of options.writes) {
|
|
3959
|
+
finalByTarget.set(liveFormFieldTargetKey(write.target) ?? JSON.stringify(write.target), { target: write.target, value: write.value });
|
|
3960
|
+
}
|
|
3961
|
+
const finalValues = [];
|
|
3962
|
+
for (const expected of finalByTarget.values()) {
|
|
3963
|
+
finalValues.push(await waitForLiveFormFieldValue(page, { target: expected.target, expectedValue: expected.value }));
|
|
3964
|
+
}
|
|
3965
|
+
return {
|
|
3966
|
+
transactions,
|
|
3967
|
+
...(quiet ? { quiet } : {}),
|
|
3968
|
+
...(validation ? { validation } : {}),
|
|
3969
|
+
finalValues,
|
|
3970
|
+
ok: transactions.every((transaction) => transaction.write.valueMatched !== false) &&
|
|
3971
|
+
(quiet?.quiet ?? true) &&
|
|
3972
|
+
((validation?.inlineErrors.length ?? 0) === 0) &&
|
|
3973
|
+
finalValues.every((value) => value.valueMatched !== false),
|
|
3974
|
+
};
|
|
3975
|
+
}
|
|
3976
|
+
export async function waitForLiveFormRerenderSettled(page, options = {}) {
|
|
3977
|
+
if (!hasEvaluate(page)) {
|
|
3978
|
+
throw new Error("waitForLiveFormRerenderSettled requires a Playwright page with evaluate().");
|
|
3979
|
+
}
|
|
3980
|
+
return page.evaluate((rerenderOptions) => new Promise((resolve) => {
|
|
3981
|
+
const rootSelector = rerenderOptions.rootSelector ?? "body";
|
|
3982
|
+
const root = document.querySelector(rootSelector);
|
|
3983
|
+
const startedAt = Date.now();
|
|
3984
|
+
const quietMs = rerenderOptions.quietMs ?? 150;
|
|
3985
|
+
const timeoutMs = rerenderOptions.timeoutMs ?? 2000;
|
|
3986
|
+
let mutationCount = 0;
|
|
3987
|
+
let quietTimer;
|
|
3988
|
+
let timeoutTimer;
|
|
3989
|
+
let observer;
|
|
3990
|
+
const finish = (quiet) => {
|
|
3991
|
+
if (observer)
|
|
3992
|
+
observer.disconnect();
|
|
3993
|
+
if (quietTimer !== undefined)
|
|
3994
|
+
window.clearTimeout(quietTimer);
|
|
3995
|
+
if (timeoutTimer !== undefined)
|
|
3996
|
+
window.clearTimeout(timeoutTimer);
|
|
3997
|
+
const elapsedMs = Date.now() - startedAt;
|
|
3998
|
+
resolve({
|
|
3999
|
+
rootSelector,
|
|
4000
|
+
mutationCount,
|
|
4001
|
+
elapsedMs,
|
|
4002
|
+
quiet,
|
|
4003
|
+
ok: quiet && mutationCount >= (rerenderOptions.expectedMinMutations ?? 0),
|
|
4004
|
+
});
|
|
4005
|
+
};
|
|
4006
|
+
if (!root) {
|
|
4007
|
+
finish(false);
|
|
4008
|
+
return;
|
|
4009
|
+
}
|
|
4010
|
+
const armQuietTimer = () => {
|
|
4011
|
+
if (quietTimer !== undefined)
|
|
4012
|
+
window.clearTimeout(quietTimer);
|
|
4013
|
+
quietTimer = window.setTimeout(() => finish(true), quietMs);
|
|
4014
|
+
};
|
|
4015
|
+
observer = new MutationObserver((records) => {
|
|
4016
|
+
mutationCount += records.length;
|
|
4017
|
+
armQuietTimer();
|
|
4018
|
+
});
|
|
4019
|
+
observer.observe(root, { attributes: true, childList: true, subtree: true });
|
|
4020
|
+
timeoutTimer = window.setTimeout(() => finish(false), timeoutMs);
|
|
4021
|
+
armQuietTimer();
|
|
4022
|
+
}), options);
|
|
4023
|
+
}
|
|
3769
4024
|
export async function runFormstackIframePrefillSmoke(page, options) {
|
|
3770
4025
|
const bridge = await sendFormstackIframePrefillMessage(page, options);
|
|
3771
4026
|
const responseData = bridge.response.record?.data;
|
|
@@ -3773,6 +4028,16 @@ export async function runFormstackIframePrefillSmoke(page, options) {
|
|
|
3773
4028
|
const ok = bridge.posted && bridge.response.matched && (options.expectResponseOk === false || responseOk);
|
|
3774
4029
|
return { bridge, ok };
|
|
3775
4030
|
}
|
|
4031
|
+
export async function runFormstackIframePrefillScenario(page, options) {
|
|
4032
|
+
const prefill = await runFormstackIframePrefillSmoke(page, options);
|
|
4033
|
+
const layoutOptions = options.layout === true ? { iframeSelector: options.iframeSelector } : options.layout || undefined;
|
|
4034
|
+
const layout = layoutOptions ? await diagnoseLiveFormIframeLayout(page, layoutOptions) : undefined;
|
|
4035
|
+
return {
|
|
4036
|
+
prefill,
|
|
4037
|
+
...(layout ? { layout } : {}),
|
|
4038
|
+
ok: prefill.ok && (layout?.ok ?? true),
|
|
4039
|
+
};
|
|
4040
|
+
}
|
|
3776
4041
|
export async function assertLiveFormConditionalLogic(page, options) {
|
|
3777
4042
|
const transaction = await performLiveFormFieldTransaction(page, {
|
|
3778
4043
|
...(options.trigger.transaction ?? {}),
|