@redrockswebdevelopment/formstack-form-testing 0.1.23 → 0.1.25

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.js CHANGED
@@ -904,9 +904,35 @@ export function buildLiveFormAddressValue(value, current = {}) {
904
904
  export function buildLiveFormChoiceValue(value, otherValue = "") {
905
905
  return { value, otherValue };
906
906
  }
907
+ export function readLiveFormChoiceValue(value) {
908
+ if (typeof value === "string")
909
+ return value;
910
+ if (!value || typeof value !== "object")
911
+ return null;
912
+ const selected = value.value;
913
+ return typeof selected === "string" && selected.length > 0 ? selected : null;
914
+ }
915
+ export function normalizeLiveFormChoiceValue(value, otherValue = "") {
916
+ if (typeof value === "string")
917
+ return buildLiveFormChoiceValue(value, otherValue);
918
+ return buildLiveFormChoiceValue(value.value, typeof value.otherValue === "string" ? value.otherValue : otherValue);
919
+ }
907
920
  export function buildLiveFormSelectValue(value) {
908
921
  return { value };
909
922
  }
923
+ export function readLiveFormSelectValue(value) {
924
+ if (typeof value === "string")
925
+ return value;
926
+ if (!value || typeof value !== "object")
927
+ return null;
928
+ const selected = value.value;
929
+ return typeof selected === "string" && selected.length > 0 ? selected : null;
930
+ }
931
+ export function normalizeLiveFormSelectValue(value) {
932
+ if (typeof value === "string")
933
+ return buildLiveFormSelectValue(value);
934
+ return buildLiveFormSelectValue(value.value);
935
+ }
910
936
  export function buildLiveFormCheckboxValue(values, otherValue = null) {
911
937
  return Object.fromEntries([...values.map((value, index) => [String(index), value]), ["otherValue", otherValue]]);
912
938
  }
@@ -1815,6 +1841,124 @@ export async function diagnoseLiveFormCommitControl(page, options) {
1815
1841
  };
1816
1842
  }, options);
1817
1843
  }
1844
+ export async function readLiveFormChoiceState(page, options) {
1845
+ if (!hasEvaluate(page)) {
1846
+ throw new Error("readLiveFormChoiceState requires a Playwright page with evaluate().");
1847
+ }
1848
+ return page.evaluate((stateOptions) => {
1849
+ const controlSnapshot = (control) => ({
1850
+ tagName: control.tagName.toLowerCase(),
1851
+ type: control.getAttribute("type"),
1852
+ id: control.getAttribute("id"),
1853
+ name: control.getAttribute("name"),
1854
+ value: control.getAttribute("value"),
1855
+ currentValue: control.value,
1856
+ checked: control.checked,
1857
+ });
1858
+ const root = stateOptions.selector != null
1859
+ ? document.querySelector(stateOptions.selector)
1860
+ : stateOptions.internalLabel != null
1861
+ ? document.querySelector(`[data-internal-label="${CSS.escape(stateOptions.internalLabel)}"]`)
1862
+ : stateOptions.fieldId == null
1863
+ ? null
1864
+ : document.querySelector(`#fsCell${CSS.escape(String(stateOptions.fieldId))}, #fsFieldCell${CSS.escape(String(stateOptions.fieldId))}, [data-testid="field-${CSS.escape(String(stateOptions.fieldId))}"]`);
1865
+ const controls = root
1866
+ ? Array.from(root.querySelectorAll("input[type=\"radio\"]")).map(controlSnapshot)
1867
+ : [];
1868
+ const selectedControls = controls.filter((control) => control.checked === true);
1869
+ const selectedValue = selectedControls.length === 1
1870
+ ? selectedControls[0]?.currentValue ?? selectedControls[0]?.value ?? null
1871
+ : null;
1872
+ const allValues = controls
1873
+ .map((control) => control.currentValue ?? control.value ?? "")
1874
+ .filter((value) => value.length > 0);
1875
+ return {
1876
+ fieldMatched: Boolean(root),
1877
+ selectedValue,
1878
+ allValues,
1879
+ ...(stateOptions.expectedValue === undefined ? {} : { expectedValue: stateOptions.expectedValue }),
1880
+ matched: Boolean(root) &&
1881
+ selectedControls.length === 1 &&
1882
+ (stateOptions.expectedValue === undefined || selectedValue === stateOptions.expectedValue),
1883
+ controls,
1884
+ };
1885
+ }, options);
1886
+ }
1887
+ export async function waitForLiveFormChoiceState(page, options) {
1888
+ const timeoutMs = options.timeoutMs ?? 10_000;
1889
+ const intervalMs = options.intervalMs ?? 100;
1890
+ const startedAt = Date.now();
1891
+ let latest = await readLiveFormChoiceState(page, options);
1892
+ while (!latest.matched && Date.now() - startedAt < timeoutMs) {
1893
+ await delay(intervalMs);
1894
+ latest = await readLiveFormChoiceState(page, options);
1895
+ }
1896
+ return latest;
1897
+ }
1898
+ export async function readLiveFormSelectState(page, options) {
1899
+ if (!hasEvaluate(page)) {
1900
+ throw new Error("readLiveFormSelectState requires a Playwright page with evaluate().");
1901
+ }
1902
+ return page.evaluate((stateOptions) => {
1903
+ const controlSnapshot = (control) => ({
1904
+ tagName: control.tagName.toLowerCase(),
1905
+ type: control.getAttribute("type"),
1906
+ id: control.getAttribute("id"),
1907
+ name: control.getAttribute("name"),
1908
+ value: control.getAttribute("value"),
1909
+ currentValue: control.value,
1910
+ checked: null,
1911
+ });
1912
+ const root = stateOptions.selector != null
1913
+ ? document.querySelector(stateOptions.selector)
1914
+ : stateOptions.internalLabel != null
1915
+ ? document.querySelector(`[data-internal-label="${CSS.escape(stateOptions.internalLabel)}"]`)
1916
+ : stateOptions.fieldId == null
1917
+ ? null
1918
+ : document.querySelector(`#fsCell${CSS.escape(String(stateOptions.fieldId))}, #fsFieldCell${CSS.escape(String(stateOptions.fieldId))}, [data-testid="field-${CSS.escape(String(stateOptions.fieldId))}"]`);
1919
+ const selects = root
1920
+ ? Array.from(root.querySelectorAll("select"))
1921
+ : [];
1922
+ const controls = selects.map(controlSnapshot);
1923
+ const primary = selects[0] ?? null;
1924
+ const options = primary
1925
+ ? Array.from(primary.options).map((option) => ({
1926
+ value: option.value,
1927
+ text: option.textContent?.replace(/\s+/g, " ").trim() ?? "",
1928
+ selected: option.selected,
1929
+ }))
1930
+ : [];
1931
+ const selected = options.find((option) => option.selected) ?? null;
1932
+ const selectedValue = primary ? primary.value : null;
1933
+ const selectedText = selected?.text ?? null;
1934
+ const allValues = options
1935
+ .map((option) => option.value)
1936
+ .filter((value) => value.length > 0);
1937
+ return {
1938
+ fieldMatched: Boolean(root),
1939
+ selectedValue,
1940
+ selectedText,
1941
+ allValues,
1942
+ ...(stateOptions.expectedValue === undefined ? {} : { expectedValue: stateOptions.expectedValue }),
1943
+ matched: Boolean(root) &&
1944
+ primary !== null &&
1945
+ (stateOptions.expectedValue === undefined || selectedValue === stateOptions.expectedValue),
1946
+ controls,
1947
+ options,
1948
+ };
1949
+ }, options);
1950
+ }
1951
+ export async function waitForLiveFormSelectState(page, options) {
1952
+ const timeoutMs = options.timeoutMs ?? 10_000;
1953
+ const intervalMs = options.intervalMs ?? 100;
1954
+ const startedAt = Date.now();
1955
+ let latest = await readLiveFormSelectState(page, options);
1956
+ while (!latest.matched && Date.now() - startedAt < timeoutMs) {
1957
+ await delay(intervalMs);
1958
+ latest = await readLiveFormSelectState(page, options);
1959
+ }
1960
+ return latest;
1961
+ }
1818
1962
  export async function dispatchLiveFormFieldDomEvents(page, options) {
1819
1963
  if (!hasEvaluate(page)) {
1820
1964
  throw new Error("dispatchLiveFormFieldDomEvents requires a Playwright page with evaluate().");
@@ -3405,6 +3549,92 @@ export async function setLiveFormCheckboxValues(page, options) {
3405
3549
  (commitControl?.ready ?? true),
3406
3550
  };
3407
3551
  }
3552
+ export async function setLiveFormChoiceValue(page, options) {
3553
+ const payload = normalizeLiveFormChoiceValue(options.value);
3554
+ const value = payload.value;
3555
+ const transaction = await performLiveFormFieldTransaction(page, {
3556
+ write: {
3557
+ ...(options.write ?? {}),
3558
+ target: options.target,
3559
+ value: payload,
3560
+ notify: options.write?.notify ?? true,
3561
+ },
3562
+ fieldKind: "radio",
3563
+ waitForEvent: options.waitForEvent ?? true,
3564
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
3565
+ afterQuiet: options.afterQuiet ?? { quietMs: 200, timeoutMs: 5_000 },
3566
+ ...(options.afterValidation === undefined ? {} : { afterValidation: options.afterValidation }),
3567
+ });
3568
+ const dom = await waitForLiveFormChoiceState(page, {
3569
+ ...options.target,
3570
+ expectedValue: value,
3571
+ ...(options.write?.timeoutMs === undefined ? {} : { timeoutMs: options.write.timeoutMs }),
3572
+ });
3573
+ const visibilityTargets = [
3574
+ ...(options.expectVisible ?? []).map((target) => ({ ...target, visible: true })),
3575
+ ...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
3576
+ ];
3577
+ const visibility = visibilityTargets.length > 0
3578
+ ? await waitForLiveFormVisibility(page, { targets: visibilityTargets })
3579
+ : undefined;
3580
+ return {
3581
+ payload,
3582
+ value,
3583
+ transaction,
3584
+ dom,
3585
+ ...(visibility ? { visibility } : {}),
3586
+ ok: transaction.write.valueMatched &&
3587
+ (transaction.event?.matched ?? true) &&
3588
+ (transaction.domDispatch ? transaction.domDispatch.matchedControlCount === 1 : true) &&
3589
+ (transaction.quiet?.quiet ?? true) &&
3590
+ ((transaction.validation?.inlineErrors.length ?? 0) === 0) &&
3591
+ dom.matched &&
3592
+ (visibility?.matched ?? true),
3593
+ };
3594
+ }
3595
+ export async function setLiveFormSelectValue(page, options) {
3596
+ const payload = normalizeLiveFormSelectValue(options.value);
3597
+ const value = payload.value;
3598
+ const transaction = await performLiveFormFieldTransaction(page, {
3599
+ write: {
3600
+ ...(options.write ?? {}),
3601
+ target: options.target,
3602
+ value: payload,
3603
+ notify: options.write?.notify ?? true,
3604
+ },
3605
+ fieldKind: "select",
3606
+ waitForEvent: options.waitForEvent ?? true,
3607
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
3608
+ afterQuiet: options.afterQuiet ?? { quietMs: 200, timeoutMs: 5_000 },
3609
+ ...(options.afterValidation === undefined ? {} : { afterValidation: options.afterValidation }),
3610
+ });
3611
+ const dom = await waitForLiveFormSelectState(page, {
3612
+ ...options.target,
3613
+ expectedValue: value,
3614
+ ...(options.write?.timeoutMs === undefined ? {} : { timeoutMs: options.write.timeoutMs }),
3615
+ });
3616
+ const visibilityTargets = [
3617
+ ...(options.expectVisible ?? []).map((target) => ({ ...target, visible: true })),
3618
+ ...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
3619
+ ];
3620
+ const visibility = visibilityTargets.length > 0
3621
+ ? await waitForLiveFormVisibility(page, { targets: visibilityTargets })
3622
+ : undefined;
3623
+ return {
3624
+ payload,
3625
+ value,
3626
+ transaction,
3627
+ dom,
3628
+ ...(visibility ? { visibility } : {}),
3629
+ ok: transaction.write.valueMatched &&
3630
+ (transaction.event?.matched ?? true) &&
3631
+ (transaction.domDispatch ? transaction.domDispatch.matchedControlCount >= 1 : true) &&
3632
+ (transaction.quiet?.quiet ?? true) &&
3633
+ ((transaction.validation?.inlineErrors.length ?? 0) === 0) &&
3634
+ dom.matched &&
3635
+ (visibility?.matched ?? true),
3636
+ };
3637
+ }
3408
3638
  export async function answerProgressiveLiveFormField(page, options) {
3409
3639
  const validationOptions = options.afterValidation === undefined ? undefined : typeof options.afterValidation === "object" ? options.afterValidation : {};
3410
3640
  const checkboxTransaction = options.fieldKind === "checkbox"
@@ -3418,21 +3648,47 @@ export async function answerProgressiveLiveFormField(page, options) {
3418
3648
  ...(validationOptions ? { afterValidation: validationOptions } : {}),
3419
3649
  })
3420
3650
  : undefined;
3421
- const transaction = checkboxTransaction
3422
- ? checkboxTransaction.transaction
3423
- : await performLiveFormFieldTransaction(page, {
3424
- write: {
3425
- ...(options.write ?? {}),
3426
- target: options.target,
3427
- value: options.value,
3428
- notify: options.write?.notify ?? true,
3429
- },
3430
- ...(options.fieldKind ? { fieldKind: options.fieldKind } : {}),
3651
+ const choiceTransaction = !checkboxTransaction && options.fieldKind === "radio"
3652
+ ? await setLiveFormChoiceValue(page, {
3653
+ target: options.target,
3654
+ value: options.value,
3655
+ ...(options.write === undefined ? {} : { write: options.write }),
3431
3656
  waitForEvent: options.waitForEvent ?? true,
3432
3657
  dispatchDomEvents: options.dispatchDomEvents ?? "auto",
3433
3658
  afterQuiet: options.afterQuiet ?? true,
3434
3659
  ...(validationOptions ? { afterValidation: validationOptions } : {}),
3435
- });
3660
+ })
3661
+ : undefined;
3662
+ const selectTransaction = !checkboxTransaction && !choiceTransaction && options.fieldKind === "select"
3663
+ ? await setLiveFormSelectValue(page, {
3664
+ target: options.target,
3665
+ value: options.value,
3666
+ ...(options.write === undefined ? {} : { write: options.write }),
3667
+ waitForEvent: options.waitForEvent ?? true,
3668
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
3669
+ afterQuiet: options.afterQuiet ?? true,
3670
+ ...(validationOptions ? { afterValidation: validationOptions } : {}),
3671
+ })
3672
+ : undefined;
3673
+ const transaction = checkboxTransaction
3674
+ ? checkboxTransaction.transaction
3675
+ : choiceTransaction
3676
+ ? choiceTransaction.transaction
3677
+ : selectTransaction
3678
+ ? selectTransaction.transaction
3679
+ : await performLiveFormFieldTransaction(page, {
3680
+ write: {
3681
+ ...(options.write ?? {}),
3682
+ target: options.target,
3683
+ value: options.value,
3684
+ notify: options.write?.notify ?? true,
3685
+ },
3686
+ ...(options.fieldKind ? { fieldKind: options.fieldKind } : {}),
3687
+ waitForEvent: options.waitForEvent ?? true,
3688
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
3689
+ afterQuiet: options.afterQuiet ?? true,
3690
+ ...(validationOptions ? { afterValidation: validationOptions } : {}),
3691
+ });
3436
3692
  const visibilityTargets = [
3437
3693
  ...(options.expectVisible ?? []),
3438
3694
  ...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
@@ -3635,7 +3891,7 @@ export function normalizeLiveFormSampleValue(kind = "unknown", value) {
3635
3891
  if (value instanceof Date)
3636
3892
  return value.toISOString().slice(0, 10);
3637
3893
  if (["radio", "rating", "select"].includes(kind) && typeof value === "string") {
3638
- return { value };
3894
+ return kind === "radio" ? normalizeLiveFormChoiceValue(value) : { value };
3639
3895
  }
3640
3896
  if (kind === "checkbox" && (Array.isArray(value) || (value && typeof value === "object"))) {
3641
3897
  return normalizeLiveFormCheckboxValue(value);
@@ -4854,8 +5110,32 @@ export const liveFormFieldRecipes = {
4854
5110
  }),
4855
5111
  setNameApi: (name, internalLabel, value, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormNameValue(value), options),
4856
5112
  setAddressApi: (name, internalLabel, value, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormAddressValue(value), options),
4857
- setChoiceApi: (name, internalLabel, value, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormChoiceValue(value), options),
4858
- setSelectApi: (name, internalLabel, value, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormSelectValue(value), options),
5113
+ setChoiceApi: (name, internalLabel, value, options = {}) => ({
5114
+ name,
5115
+ run: async (harness) => {
5116
+ await setLiveFormChoiceValue(harness.page, {
5117
+ target: { internalLabel },
5118
+ value,
5119
+ write: { notify: options.notify ?? true },
5120
+ waitForEvent: options.waitForEvent ?? (options.notify ?? true),
5121
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
5122
+ afterQuiet: options.waitForQuiet ?? true,
5123
+ });
5124
+ },
5125
+ }),
5126
+ setSelectApi: (name, internalLabel, value, options = {}) => ({
5127
+ name,
5128
+ run: async (harness) => {
5129
+ await setLiveFormSelectValue(harness.page, {
5130
+ target: { internalLabel },
5131
+ value,
5132
+ write: { notify: options.notify ?? true },
5133
+ waitForEvent: options.waitForEvent ?? (options.notify ?? true),
5134
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
5135
+ afterQuiet: options.waitForQuiet ?? true,
5136
+ });
5137
+ },
5138
+ }),
4859
5139
  setCheckboxesApi: (name, internalLabel, values, options = {}) => ({
4860
5140
  name,
4861
5141
  run: async (harness) => {