@redrockswebdevelopment/formstack-form-testing 0.1.25 → 0.1.26

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
@@ -987,6 +987,22 @@ export function buildLiveFormMatrixValue(selections) {
987
987
  }),
988
988
  };
989
989
  }
990
+ export function readLiveFormMatrixSelections(value) {
991
+ if (Array.isArray(value)) {
992
+ return value.filter((entry) => typeof entry === "string");
993
+ }
994
+ if (!value || typeof value !== "object")
995
+ return [];
996
+ const selections = value.value;
997
+ return Array.isArray(selections)
998
+ ? selections.filter((entry) => typeof entry === "string")
999
+ : [];
1000
+ }
1001
+ export function normalizeLiveFormMatrixValue(selections) {
1002
+ if (Array.isArray(selections))
1003
+ return buildLiveFormMatrixValue(selections);
1004
+ return buildLiveFormMatrixValue(readLiveFormMatrixSelections(selections));
1005
+ }
990
1006
  export async function readLiveFormFieldValue(page, options) {
991
1007
  if (!hasEvaluate(page)) {
992
1008
  throw new Error("readLiveFormFieldValue requires a Playwright page with evaluate().");
@@ -1959,6 +1975,104 @@ export async function waitForLiveFormSelectState(page, options) {
1959
1975
  }
1960
1976
  return latest;
1961
1977
  }
1978
+ export async function readLiveFormMatrixState(page, options) {
1979
+ if (!hasEvaluate(page)) {
1980
+ throw new Error("readLiveFormMatrixState requires a Playwright page with evaluate().");
1981
+ }
1982
+ return page.evaluate((stateOptions) => {
1983
+ const normalizeText = (value) => (value ?? "").replace(/\s+/g, " ").trim();
1984
+ const controlSnapshot = (control) => ({
1985
+ tagName: control.tagName.toLowerCase(),
1986
+ type: control.getAttribute("type"),
1987
+ id: control.getAttribute("id"),
1988
+ name: control.getAttribute("name"),
1989
+ value: control.getAttribute("value"),
1990
+ currentValue: control.value,
1991
+ checked: control.checked,
1992
+ });
1993
+ const root = stateOptions.selector != null
1994
+ ? document.querySelector(stateOptions.selector)
1995
+ : stateOptions.internalLabel != null
1996
+ ? document.querySelector(`[data-internal-label="${CSS.escape(stateOptions.internalLabel)}"]`)
1997
+ : stateOptions.fieldId == null
1998
+ ? null
1999
+ : document.querySelector(`#fsCell${CSS.escape(String(stateOptions.fieldId))}, #fsFieldCell${CSS.escape(String(stateOptions.fieldId))}, [data-testid="field-${CSS.escape(String(stateOptions.fieldId))}"]`);
2000
+ const controls = root
2001
+ ? Array.from(root.querySelectorAll("input[type=\"radio\"], input[type=\"checkbox\"]"))
2002
+ : [];
2003
+ const inferCell = (control) => {
2004
+ const tableCell = control.closest("td, th");
2005
+ const tableRow = control.closest("tr");
2006
+ const table = control.closest("table");
2007
+ const rowHeader = tableRow
2008
+ ? tableRow.querySelector("th") ??
2009
+ Array.from(tableRow.children).find((child) => !child.contains(control))
2010
+ : null;
2011
+ const columnIndex = tableCell?.cellIndex ?? -1;
2012
+ const headerRows = table ? Array.from(table.querySelectorAll("thead tr, tr")).filter((row) => !row.contains(control)) : [];
2013
+ const columnHeader = columnIndex >= 0
2014
+ ? headerRows
2015
+ .map((row) => row.children.item(columnIndex))
2016
+ .find((cell) => cell && normalizeText(cell.textContent).length > 0) ?? null
2017
+ : null;
2018
+ const ariaLabel = control.getAttribute("aria-label");
2019
+ const label = control.id ? root?.querySelector(`label[for="${CSS.escape(control.id)}"]`) : null;
2020
+ const row = normalizeText(control.getAttribute("data-row-label")) ||
2021
+ normalizeText(rowHeader?.textContent) ||
2022
+ normalizeText(control.getAttribute("data-row")) ||
2023
+ null;
2024
+ const column = normalizeText(control.getAttribute("data-column-label")) ||
2025
+ normalizeText(columnHeader?.textContent) ||
2026
+ normalizeText(control.getAttribute("data-column")) ||
2027
+ normalizeText(ariaLabel) ||
2028
+ normalizeText(label?.textContent) ||
2029
+ normalizeText(control.value) ||
2030
+ null;
2031
+ const selection = row && column ? `${row} = ${column}` : null;
2032
+ return {
2033
+ row,
2034
+ column,
2035
+ value: control.value,
2036
+ selection,
2037
+ checked: control.checked,
2038
+ control: controlSnapshot(control),
2039
+ };
2040
+ };
2041
+ const cells = controls.map(inferCell);
2042
+ const selectedSelections = cells
2043
+ .filter((cell) => cell.checked && cell.selection)
2044
+ .map((cell) => cell.selection);
2045
+ const allSelections = cells
2046
+ .map((cell) => cell.selection)
2047
+ .filter((selection) => typeof selection === "string" && selection.length > 0);
2048
+ const expectedSelections = [...(stateOptions.expectedSelections ?? [])];
2049
+ const missingSelections = expectedSelections.filter((selection) => !selectedSelections.includes(selection));
2050
+ const unexpectedSelections = selectedSelections.filter((selection) => !expectedSelections.includes(selection));
2051
+ const exact = stateOptions.exact !== false;
2052
+ return {
2053
+ fieldMatched: Boolean(root),
2054
+ selectedSelections,
2055
+ allSelections,
2056
+ expectedSelections,
2057
+ missingSelections,
2058
+ unexpectedSelections,
2059
+ exact,
2060
+ matched: Boolean(root) && missingSelections.length === 0 && (!exact || unexpectedSelections.length === 0),
2061
+ cells,
2062
+ };
2063
+ }, options);
2064
+ }
2065
+ export async function waitForLiveFormMatrixState(page, options) {
2066
+ const timeoutMs = options.timeoutMs ?? 10_000;
2067
+ const intervalMs = options.intervalMs ?? 100;
2068
+ const startedAt = Date.now();
2069
+ let latest = await readLiveFormMatrixState(page, options);
2070
+ while (!latest.matched && Date.now() - startedAt < timeoutMs) {
2071
+ await delay(intervalMs);
2072
+ latest = await readLiveFormMatrixState(page, options);
2073
+ }
2074
+ return latest;
2075
+ }
1962
2076
  export async function dispatchLiveFormFieldDomEvents(page, options) {
1963
2077
  if (!hasEvaluate(page)) {
1964
2078
  throw new Error("dispatchLiveFormFieldDomEvents requires a Playwright page with evaluate().");
@@ -3635,6 +3749,50 @@ export async function setLiveFormSelectValue(page, options) {
3635
3749
  (visibility?.matched ?? true),
3636
3750
  };
3637
3751
  }
3752
+ export async function setLiveFormMatrixValues(page, options) {
3753
+ const payload = normalizeLiveFormMatrixValue(options.selections);
3754
+ const selections = payload.value;
3755
+ const transaction = await performLiveFormFieldTransaction(page, {
3756
+ write: {
3757
+ ...(options.write ?? {}),
3758
+ target: options.target,
3759
+ value: payload,
3760
+ notify: options.write?.notify ?? true,
3761
+ },
3762
+ fieldKind: "matrix",
3763
+ waitForEvent: options.waitForEvent ?? true,
3764
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
3765
+ afterQuiet: options.afterQuiet ?? { quietMs: 200, timeoutMs: 5_000 },
3766
+ ...(options.afterValidation === undefined ? {} : { afterValidation: options.afterValidation }),
3767
+ });
3768
+ const dom = await waitForLiveFormMatrixState(page, {
3769
+ ...options.target,
3770
+ expectedSelections: selections,
3771
+ exact: options.exactDomSelection ?? true,
3772
+ ...(options.write?.timeoutMs === undefined ? {} : { timeoutMs: options.write.timeoutMs }),
3773
+ });
3774
+ const visibilityTargets = [
3775
+ ...(options.expectVisible ?? []).map((target) => ({ ...target, visible: true })),
3776
+ ...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
3777
+ ];
3778
+ const visibility = visibilityTargets.length > 0
3779
+ ? await waitForLiveFormVisibility(page, { targets: visibilityTargets })
3780
+ : undefined;
3781
+ return {
3782
+ payload,
3783
+ selections,
3784
+ transaction,
3785
+ dom,
3786
+ ...(visibility ? { visibility } : {}),
3787
+ ok: transaction.write.valueMatched &&
3788
+ (transaction.event?.matched ?? true) &&
3789
+ (transaction.domDispatch ? transaction.domDispatch.matchedControlCount >= selections.length : true) &&
3790
+ (transaction.quiet?.quiet ?? true) &&
3791
+ ((transaction.validation?.inlineErrors.length ?? 0) === 0) &&
3792
+ dom.matched &&
3793
+ (visibility?.matched ?? true),
3794
+ };
3795
+ }
3638
3796
  export async function answerProgressiveLiveFormField(page, options) {
3639
3797
  const validationOptions = options.afterValidation === undefined ? undefined : typeof options.afterValidation === "object" ? options.afterValidation : {};
3640
3798
  const checkboxTransaction = options.fieldKind === "checkbox"
@@ -3670,25 +3828,38 @@ export async function answerProgressiveLiveFormField(page, options) {
3670
3828
  ...(validationOptions ? { afterValidation: validationOptions } : {}),
3671
3829
  })
3672
3830
  : undefined;
3831
+ const matrixTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && options.fieldKind === "matrix"
3832
+ ? await setLiveFormMatrixValues(page, {
3833
+ target: options.target,
3834
+ selections: options.value,
3835
+ ...(options.write === undefined ? {} : { write: options.write }),
3836
+ waitForEvent: options.waitForEvent ?? true,
3837
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
3838
+ afterQuiet: options.afterQuiet ?? true,
3839
+ ...(validationOptions ? { afterValidation: validationOptions } : {}),
3840
+ })
3841
+ : undefined;
3673
3842
  const transaction = checkboxTransaction
3674
3843
  ? checkboxTransaction.transaction
3675
3844
  : choiceTransaction
3676
3845
  ? choiceTransaction.transaction
3677
3846
  : selectTransaction
3678
3847
  ? 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
- });
3848
+ : matrixTransaction
3849
+ ? matrixTransaction.transaction
3850
+ : await performLiveFormFieldTransaction(page, {
3851
+ write: {
3852
+ ...(options.write ?? {}),
3853
+ target: options.target,
3854
+ value: options.value,
3855
+ notify: options.write?.notify ?? true,
3856
+ },
3857
+ ...(options.fieldKind ? { fieldKind: options.fieldKind } : {}),
3858
+ waitForEvent: options.waitForEvent ?? true,
3859
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
3860
+ afterQuiet: options.afterQuiet ?? true,
3861
+ ...(validationOptions ? { afterValidation: validationOptions } : {}),
3862
+ });
3692
3863
  const visibilityTargets = [
3693
3864
  ...(options.expectVisible ?? []),
3694
3865
  ...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
@@ -5150,7 +5321,19 @@ export const liveFormFieldRecipes = {
5150
5321
  },
5151
5322
  }),
5152
5323
  setDateTimeApi: (name, internalLabel, value, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormDateTimeValue(value), options),
5153
- setMatrixApi: (name, internalLabel, selections, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormMatrixValue(selections), options),
5324
+ setMatrixApi: (name, internalLabel, selections, options = {}) => ({
5325
+ name,
5326
+ run: async (harness) => {
5327
+ await setLiveFormMatrixValues(harness.page, {
5328
+ target: { internalLabel },
5329
+ selections,
5330
+ write: { notify: options.notify ?? true },
5331
+ waitForEvent: options.waitForEvent ?? (options.notify ?? true),
5332
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
5333
+ afterQuiet: options.waitForQuiet ?? true,
5334
+ });
5335
+ },
5336
+ }),
5154
5337
  setRatingApi: (name, internalLabel, value, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormRatingValue(value), options),
5155
5338
  setProductApi: (name, internalLabel, value, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormProductValue(value), options),
5156
5339
  setFieldApiAuto: (name, internalLabel, value, fieldKind, options = {}) => apiRecipeStep(name, { internalLabel }, value, { ...options, fieldKind, dispatchDomEvents: "auto" }),