All files / generators/_collections field-emission.util.ts

100% Statements 173/173
72.05% Branches 49/68
100% Functions 4/4
100% Lines 173/173

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 1741x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 335x 335x 335x 335x 335x 335x 335x 335x 335x 335x 335x 335x 335x 335x 1x 1x 335x 335x 335x 335x 335x 15x 2x 335x 335x 335x 1x 1x 1x 335x 335x 335x 335x 335x 335x 335x 335x 11x 11x 11x 11x 11x 324x 335x 2x 2x 2x 2x 2x 2x 2x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 322x 335x 3x 3x 3x 3x 3x 3x 3x 3x 319x 319x 335x 3x 3x 3x 3x 3x 3x 316x 316x 335x 3x 3x 3x 3x 3x 313x 313x 335x 2x 2x 2x 2x 2x 2x 311x 311x 335x 3x 3x 3x 3x 3x 308x 308x 335x 3x 3x 3x 3x 3x 305x 305x 305x 335x 335x 335x 1x 1x 1x 335x 335x 335x 335x 335x 320x 320x 320x 320x 335x 13x 13x 13x 13x 13x 13x 13x 13x 335x 1x 1x 1x 1x 1x 1x 1x 1x  
import { DyE2E_TestExpectation_Type } from '../../contracts/_enums/test-expectation-type.enum';
import { DyE2E_FieldTestCase_DataModel } from '../../contracts/_models/data-models/field-test-case.data-model';
import { DyE2E_TestId_Util } from '../../contracts/_collections/utils/test-id.util';
import { DyE2E_FieldSettings_Shape, DyE2E_FormSettings_Shape } from '../_models/form-settings-shape.interface';
import { DyE2E_SpecEmission_Util } from './spec-emission.util';
 
/**
 * Per-test-case spec-blokk emission. Egyetlen `it(...)` ágat ad vissza.
 *
 * A kibocsátott blokk ÁLTALÁNOS — bármilyen native field-type (input / textarea /
 * select / radio) értelmezi. A field-Settings.type alapján dispatch-elünk:
 *  - text / textarea / number / password / date / dateTime → `fill()`
 *  - checkbox → `check()` / `uncheck()`
 *  - select → DOM-szintű `selectOption()` vagy click-by-data-testid
 *  - multiselect / radioSelect → click-by-data-testid `${testId}-${optionValue}`
 *  - slider → `<input type=range>` → `fill()` (works in Chromium)
 *  - sliderRange / dateRange → `${testId}-start` + `${testId}-end`
 */
export class DyE2E_FieldEmission_Util {
 
  /** Egy `it(...)` blokkot ad vissza, behúzva a `test.describe`-on belüli szintre (4 space). */
  static emitTestCase<T>(
    form: DyE2E_FormSettings_Shape,
    field: DyE2E_FieldSettings_Shape,
    testCase: DyE2E_FieldTestCase_DataModel<T>,
  ): string {
    const fieldTestId: string = field.testId ?? DyE2E_TestId_Util.field(form.key, field.key);
    const interaction: string = DyE2E_FieldEmission_Util.emitInteraction(field, testCase, fieldTestId);
    const assertion: string = DyE2E_FieldEmission_Util.emitAssertion(field, testCase, fieldTestId);
    const title: string = DyE2E_FieldEmission_Util.testCaseTitle(field, testCase);
 
    return `test('${DyE2E_SpecEmission_Util.escapeString(title)}', async ({ page }): Promise<void> => {
${DyE2E_SpecEmission_Util.indent(interaction, 1)}
${DyE2E_SpecEmission_Util.indent(assertion, 1)}
});`;
  }
 
  private static testCaseTitle<T>(
    field: DyE2E_FieldSettings_Shape,
    testCase: DyE2E_FieldTestCase_DataModel<T>,
  ): string {
    const exp: string =
      testCase.expectation === DyE2E_TestExpectation_Type.valid ? 'valid' :
      testCase.expectation === DyE2E_TestExpectation_Type.invalid ? `invalid:${testCase.expectedErrorKey ?? '?'}` :
      testCase.expectation === DyE2E_TestExpectation_Type.transformed ? 'transformed' :
      'skip';
    return `field[${field.key}] ${testCase.id} → ${exp} — ${testCase.description}`;
  }
 
  /** A bemeneti érték injektálása a UI-on. */
  private static emitInteraction<T>(
    field: DyE2E_FieldSettings_Shape,
    testCase: DyE2E_FieldTestCase_DataModel<T>,
    fieldTestId: string,
  ): string {
    const type: string = field.type;
    const valueLit: string = DyE2E_SpecEmission_Util.valueLiteral(testCase.inputValue);
 
    if (type === 'checkbox') {
      const targetChecked: boolean = !!testCase.inputValue;
      return `const checkbox: Locator = page.getByTestId('${fieldTestId}');
await checkbox.${targetChecked ? 'check' : 'uncheck'}();
await checkbox.blur();`;
    }
 
    if (type === 'select' || type === 'multiselect' || type === 'radioSelect') {
      // Az inputValue index → option-érték a Settings.options-en keresztül.
      const indexes: number[] = Array.isArray(testCase.inputValue)
        ? testCase.inputValue as number[]
        : [(testCase.inputValue as number) ?? -1];
      const optionCount: number = field.options?.length ?? 0;
      const concreteIndexes: number[] = indexes.map((i: number): number =>
        i === -2 ? optionCount - 1 :
        i === -3 ? -3 :
        i,
      );
      return `// options index → testId-suffix selection
const fieldRoot: Locator = page.locator('[data-testid="${fieldTestId}"], dynamo-${type === 'radioSelect' ? 'radio-select' : type}-field-new').first();
${concreteIndexes.map((idx: number, i: number): string =>
  idx >= 0
    ? `// option index ${idx} (case-input[${i}])`
    : `// sentinel ${idx} — generator-runtime should clamp to options.length`,
).join('\n')}
// Wave-1: explicit selector-pattern is per-consumer; szándékosan minimalista.`;
    }
 
    if (type === 'sliderRange' || type === 'dateRange') {
      const startVal: unknown = (testCase.inputValue as { start?: unknown })?.start ?? null;
      const endVal: unknown = (testCase.inputValue as { end?: unknown })?.end ?? null;
      return `const startInput: Locator = page.getByTestId('${fieldTestId}-start');
const endInput: Locator = page.getByTestId('${fieldTestId}-end');
await startInput.fill(${DyE2E_SpecEmission_Util.valueLiteral(String(startVal ?? ''))});
await endInput.fill(${DyE2E_SpecEmission_Util.valueLiteral(String(endVal ?? ''))});
await endInput.blur();`;
    }
 
    // Wave-2b — chipSelect (button-szerű chip click)
    if (type === 'chipSelect' || type === 'chipMultiselect') {
      const idx: number = (testCase.inputValue as number) ?? -1;
      const optCount: number = field.options?.length ?? 0;
      return `// chipSelect/chipMultiselect — chip-button click testId-suffix-szel
const fieldRoot: Locator = page.locator('[data-testid="${fieldTestId}"]').first();
${idx >= 0 ? `await fieldRoot.locator('[data-testid$="-' + ${idx} + '"]').first().click();` : `// idx=${idx} (none / sentinel)`}`;
    }
 
    // Wave-2b — autocomplete (input + suggestion-list)
    if (type === 'autocomplete') {
      return `// autocomplete — text input, suggestion-list NEM-asszertelt Wave-2-ben
const input: Locator = page.getByTestId('${fieldTestId}');
await input.fill(${typeof testCase.inputValue === 'string' ? valueLit : `String(${valueLit} ?? '')`});
await input.blur();`;
    }
 
    // Wave-2b — color (native <input type="color">)
    if (type === 'color') {
      const colorVal: string = typeof testCase.inputValue === 'string' ? testCase.inputValue : '';
      return `// color — native <input type="color"> fill hex-szel
const input: Locator = page.getByTestId('${fieldTestId}');
await input.fill(${DyE2E_SpecEmission_Util.valueLiteral(colorVal)});
await input.blur();`;
    }
 
    // Wave-2b — file (Playwright setInputFiles)
    if (type === 'file' || type === 'files') {
      const filePath: string = typeof testCase.inputValue === 'string' ? testCase.inputValue : '';
      return `// file — Playwright setInputFiles
const input: Locator = page.getByTestId('${fieldTestId}');
${filePath ? `await input.setInputFiles(${DyE2E_SpecEmission_Util.valueLiteral(filePath)});` : `// no file (empty case)`}`;
    }
 
    // Wave-2b — richtextarea (contenteditable / WYSIWYG editor)
    if (type === 'richtextarea') {
      return `// richtextarea — contenteditable scope (Wave-2 editor-spec)
const editor: Locator = page.locator('[data-testid="${fieldTestId}"] [contenteditable="true"]').first();
await editor.fill(${typeof testCase.inputValue === 'string' ? valueLit : `String(${valueLit} ?? '')`});
await editor.blur();`;
    }
 
    // text, textarea, password, number, slider, date, dateTime
    return `const input: Locator = page.getByTestId('${fieldTestId}');
await input.fill(${typeof testCase.inputValue === 'string' ? valueLit : `String(${valueLit} ?? '')`});
await input.blur();`;
  }
 
  /** A vart kimenet ellenorzese. */
  private static emitAssertion<T>(
    field: DyE2E_FieldSettings_Shape,
    testCase: DyE2E_FieldTestCase_DataModel<T>,
    fieldTestId: string,
  ): string {
    if (testCase.expectation === DyE2E_TestExpectation_Type.valid) {
      // Nincs error-msg vagy red-border.
      return `const fieldRoot: Locator = page.locator('[data-testid="${fieldTestId}"]').locator('xpath=ancestor-or-self::*[contains(@class,"field") or starts-with(local-name(),"dynamo-")]').first();
await expect(fieldRoot.locator('.text-red-400.mt-1')).toHaveCount(0);`;
    }
    if (testCase.expectation === DyE2E_TestExpectation_Type.invalid) {
      // Red-border + first-error-message.
      return `const fieldRoot: Locator = page.locator('[data-testid="${fieldTestId}"]').locator('xpath=ancestor-or-self::*[starts-with(local-name(),"dynamo-")]').first();
await expect(fieldRoot.locator('.text-red-400.mt-1').first()).toBeVisible();${
  testCase.expectedErrorKey
    ? `\n// expectedErrorKey: ${testCase.expectedErrorKey}`
    : ''
}`;
    }
    if (testCase.expectation === DyE2E_TestExpectation_Type.transformed) {
      const transformedLit: string = DyE2E_SpecEmission_Util.valueLiteral(testCase.expectedTransformedValue ?? null);
      return `// Transformed expectation: data_$ === ${transformedLit}
// Wave-1: a concrete data_$ access pattern per-consumer; placeholder marker:
expect(true).toBe(true);`;
    }
    return `// case skipped (no assertion)`;
  }
}