@redrockswebdevelopment/formstack-form-testing 0.1.26 → 0.1.28

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
@@ -978,6 +978,19 @@ export function buildLiveFormProductValue(value, current = {}) {
978
978
  export function buildLiveFormRatingValue(value) {
979
979
  return { value: String(value) };
980
980
  }
981
+ export function readLiveFormRatingValue(value) {
982
+ if (typeof value === "string" || typeof value === "number")
983
+ return String(value);
984
+ if (!value || typeof value !== "object")
985
+ return null;
986
+ const selected = value.value;
987
+ return typeof selected === "string" || typeof selected === "number" ? String(selected) : null;
988
+ }
989
+ export function normalizeLiveFormRatingValue(value) {
990
+ if (typeof value === "string" || typeof value === "number")
991
+ return buildLiveFormRatingValue(value);
992
+ return buildLiveFormRatingValue(value.value);
993
+ }
981
994
  export function buildLiveFormMatrixValue(selections) {
982
995
  return {
983
996
  value: selections.map((selection) => {
@@ -2028,7 +2041,11 @@ export async function readLiveFormMatrixState(page, options) {
2028
2041
  normalizeText(label?.textContent) ||
2029
2042
  normalizeText(control.value) ||
2030
2043
  null;
2031
- const selection = row && column ? `${row} = ${column}` : null;
2044
+ const selection = row && column
2045
+ ? `${row} = ${column}`
2046
+ : control.value.length > 0
2047
+ ? control.value
2048
+ : null;
2032
2049
  return {
2033
2050
  row,
2034
2051
  column,
@@ -2073,6 +2090,88 @@ export async function waitForLiveFormMatrixState(page, options) {
2073
2090
  }
2074
2091
  return latest;
2075
2092
  }
2093
+ export async function readLiveFormRatingState(page, options) {
2094
+ if (!hasEvaluate(page)) {
2095
+ throw new Error("readLiveFormRatingState requires a Playwright page with evaluate().");
2096
+ }
2097
+ return page.evaluate((stateOptions) => {
2098
+ const normalizeText = (value) => (value ?? "").replace(/\s+/g, " ").trim();
2099
+ const root = stateOptions.selector != null
2100
+ ? document.querySelector(stateOptions.selector)
2101
+ : stateOptions.internalLabel != null
2102
+ ? document.querySelector(`[data-internal-label="${CSS.escape(stateOptions.internalLabel)}"]`)
2103
+ : stateOptions.fieldId == null
2104
+ ? null
2105
+ : document.querySelector(`#fsCell${CSS.escape(String(stateOptions.fieldId))}, #fsFieldCell${CSS.escape(String(stateOptions.fieldId))}, [data-testid="field-${CSS.escape(String(stateOptions.fieldId))}"]`);
2106
+ const candidates = root
2107
+ ? Array.from(root.querySelectorAll("input, button, [role=\"radio\"], [role=\"button\"], [aria-checked], [aria-pressed], [aria-selected], [data-value], [data-rating], [data-score]"))
2108
+ : [];
2109
+ const controlValue = (element) => {
2110
+ const htmlInput = element instanceof HTMLInputElement ? element.value : "";
2111
+ const text = normalizeText(element.textContent);
2112
+ return element.getAttribute("value") ??
2113
+ (htmlInput.length > 0 ? htmlInput : null) ??
2114
+ element.getAttribute("data-value") ??
2115
+ element.getAttribute("data-rating") ??
2116
+ element.getAttribute("data-score") ??
2117
+ element.getAttribute("aria-label")?.match(/\d+(?:\.\d+)?/)?.[0] ??
2118
+ (text.length > 0 ? text : null);
2119
+ };
2120
+ const isSelected = (element) => {
2121
+ const checked = element instanceof HTMLInputElement ? element.checked : null;
2122
+ const ariaChecked = element.getAttribute("aria-checked");
2123
+ const ariaPressed = element.getAttribute("aria-pressed");
2124
+ const ariaSelected = element.getAttribute("aria-selected");
2125
+ const classes = Array.from(element.classList).join(" ");
2126
+ return checked === true ||
2127
+ ariaChecked === "true" ||
2128
+ ariaPressed === "true" ||
2129
+ ariaSelected === "true" ||
2130
+ /\b(active|checked|selected|filled|on)\b/i.test(classes);
2131
+ };
2132
+ const controls = candidates
2133
+ .map((element) => ({
2134
+ tagName: element.tagName.toLowerCase(),
2135
+ id: element.getAttribute("id"),
2136
+ value: controlValue(element),
2137
+ text: normalizeText(element.textContent),
2138
+ ariaChecked: element.getAttribute("aria-checked"),
2139
+ ariaPressed: element.getAttribute("aria-pressed"),
2140
+ ariaSelected: element.getAttribute("aria-selected"),
2141
+ checked: element instanceof HTMLInputElement ? element.checked : null,
2142
+ selected: isSelected(element),
2143
+ }))
2144
+ .filter((control) => control.value !== null && control.value.length > 0);
2145
+ const selectedControls = controls.filter((control) => control.selected);
2146
+ const selectedValue = selectedControls.length > 0
2147
+ ? selectedControls[selectedControls.length - 1]?.value ?? null
2148
+ : null;
2149
+ const allValues = controls
2150
+ .map((control) => control.value)
2151
+ .filter((value) => typeof value === "string" && value.length > 0);
2152
+ return {
2153
+ fieldMatched: Boolean(root),
2154
+ selectedValue,
2155
+ allValues,
2156
+ ...(stateOptions.expectedValue === undefined ? {} : { expectedValue: stateOptions.expectedValue }),
2157
+ matched: Boolean(root) &&
2158
+ selectedValue !== null &&
2159
+ (stateOptions.expectedValue === undefined || selectedValue === stateOptions.expectedValue),
2160
+ controls,
2161
+ };
2162
+ }, options);
2163
+ }
2164
+ export async function waitForLiveFormRatingState(page, options) {
2165
+ const timeoutMs = options.timeoutMs ?? 10_000;
2166
+ const intervalMs = options.intervalMs ?? 100;
2167
+ const startedAt = Date.now();
2168
+ let latest = await readLiveFormRatingState(page, options);
2169
+ while (!latest.matched && Date.now() - startedAt < timeoutMs) {
2170
+ await delay(intervalMs);
2171
+ latest = await readLiveFormRatingState(page, options);
2172
+ }
2173
+ return latest;
2174
+ }
2076
2175
  export async function dispatchLiveFormFieldDomEvents(page, options) {
2077
2176
  if (!hasEvaluate(page)) {
2078
2177
  throw new Error("dispatchLiveFormFieldDomEvents requires a Playwright page with evaluate().");
@@ -3793,6 +3892,49 @@ export async function setLiveFormMatrixValues(page, options) {
3793
3892
  (visibility?.matched ?? true),
3794
3893
  };
3795
3894
  }
3895
+ export async function setLiveFormRatingValue(page, options) {
3896
+ const payload = normalizeLiveFormRatingValue(options.value);
3897
+ const value = payload.value;
3898
+ const transaction = await performLiveFormFieldTransaction(page, {
3899
+ write: {
3900
+ ...(options.write ?? {}),
3901
+ target: options.target,
3902
+ value: payload,
3903
+ notify: options.write?.notify ?? true,
3904
+ },
3905
+ fieldKind: "rating",
3906
+ waitForEvent: options.waitForEvent ?? true,
3907
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
3908
+ afterQuiet: options.afterQuiet ?? { quietMs: 200, timeoutMs: 5_000 },
3909
+ ...(options.afterValidation === undefined ? {} : { afterValidation: options.afterValidation }),
3910
+ });
3911
+ const dom = await waitForLiveFormRatingState(page, {
3912
+ ...options.target,
3913
+ expectedValue: value,
3914
+ ...(options.write?.timeoutMs === undefined ? {} : { timeoutMs: options.write.timeoutMs }),
3915
+ });
3916
+ const visibilityTargets = [
3917
+ ...(options.expectVisible ?? []).map((target) => ({ ...target, visible: true })),
3918
+ ...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
3919
+ ];
3920
+ const visibility = visibilityTargets.length > 0
3921
+ ? await waitForLiveFormVisibility(page, { targets: visibilityTargets })
3922
+ : undefined;
3923
+ return {
3924
+ payload,
3925
+ value,
3926
+ transaction,
3927
+ dom,
3928
+ ...(visibility ? { visibility } : {}),
3929
+ ok: transaction.write.valueMatched &&
3930
+ (transaction.event?.matched ?? true) &&
3931
+ (transaction.domDispatch ? transaction.domDispatch.matchedControlCount >= 1 : true) &&
3932
+ (transaction.quiet?.quiet ?? true) &&
3933
+ ((transaction.validation?.inlineErrors.length ?? 0) === 0) &&
3934
+ dom.matched &&
3935
+ (visibility?.matched ?? true),
3936
+ };
3937
+ }
3796
3938
  export async function answerProgressiveLiveFormField(page, options) {
3797
3939
  const validationOptions = options.afterValidation === undefined ? undefined : typeof options.afterValidation === "object" ? options.afterValidation : {};
3798
3940
  const checkboxTransaction = options.fieldKind === "checkbox"
@@ -3839,6 +3981,17 @@ export async function answerProgressiveLiveFormField(page, options) {
3839
3981
  ...(validationOptions ? { afterValidation: validationOptions } : {}),
3840
3982
  })
3841
3983
  : undefined;
3984
+ const ratingTransaction = !checkboxTransaction && !choiceTransaction && !selectTransaction && !matrixTransaction && options.fieldKind === "rating"
3985
+ ? await setLiveFormRatingValue(page, {
3986
+ target: options.target,
3987
+ value: options.value,
3988
+ ...(options.write === undefined ? {} : { write: options.write }),
3989
+ waitForEvent: options.waitForEvent ?? true,
3990
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
3991
+ afterQuiet: options.afterQuiet ?? true,
3992
+ ...(validationOptions ? { afterValidation: validationOptions } : {}),
3993
+ })
3994
+ : undefined;
3842
3995
  const transaction = checkboxTransaction
3843
3996
  ? checkboxTransaction.transaction
3844
3997
  : choiceTransaction
@@ -3847,19 +4000,21 @@ export async function answerProgressiveLiveFormField(page, options) {
3847
4000
  ? selectTransaction.transaction
3848
4001
  : matrixTransaction
3849
4002
  ? 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
- });
4003
+ : ratingTransaction
4004
+ ? ratingTransaction.transaction
4005
+ : await performLiveFormFieldTransaction(page, {
4006
+ write: {
4007
+ ...(options.write ?? {}),
4008
+ target: options.target,
4009
+ value: options.value,
4010
+ notify: options.write?.notify ?? true,
4011
+ },
4012
+ ...(options.fieldKind ? { fieldKind: options.fieldKind } : {}),
4013
+ waitForEvent: options.waitForEvent ?? true,
4014
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
4015
+ afterQuiet: options.afterQuiet ?? true,
4016
+ ...(validationOptions ? { afterValidation: validationOptions } : {}),
4017
+ });
3863
4018
  const visibilityTargets = [
3864
4019
  ...(options.expectVisible ?? []),
3865
4020
  ...(options.expectHidden ?? []).map((target) => ({ ...target, visible: false })),
@@ -5334,7 +5489,19 @@ export const liveFormFieldRecipes = {
5334
5489
  });
5335
5490
  },
5336
5491
  }),
5337
- setRatingApi: (name, internalLabel, value, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormRatingValue(value), options),
5492
+ setRatingApi: (name, internalLabel, value, options = {}) => ({
5493
+ name,
5494
+ run: async (harness) => {
5495
+ await setLiveFormRatingValue(harness.page, {
5496
+ target: { internalLabel },
5497
+ value,
5498
+ write: { notify: options.notify ?? true },
5499
+ waitForEvent: options.waitForEvent ?? (options.notify ?? true),
5500
+ dispatchDomEvents: options.dispatchDomEvents ?? "auto",
5501
+ afterQuiet: options.waitForQuiet ?? true,
5502
+ });
5503
+ },
5504
+ }),
5338
5505
  setProductApi: (name, internalLabel, value, options) => apiRecipeStep(name, { internalLabel }, buildLiveFormProductValue(value), options),
5339
5506
  setFieldApiAuto: (name, internalLabel, value, fieldKind, options = {}) => apiRecipeStep(name, { internalLabel }, value, { ...options, fieldKind, dispatchDomEvents: "auto" }),
5340
5507
  setNameApiAndDispatch: (name, internalLabel, value, options = {}) => liveFormFieldRecipes.setNameApi(name, internalLabel, value, { ...options, fieldKind: options.fieldKind ?? "name", dispatchDomEvents: "auto" }),