PluginProbe
Extendify / 3.1.0
Extendify v3.1.0
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / tests / playwright / QuickEdit / wpforms / wpforms.spec.ts

wpforms.spec.ts in Extendify 3.1.0, at tests/playwright/QuickEdit/wpforms/wpforms.spec.ts

195 lines 7.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { expect, test } from '../../fixtures';
2
3 // Characterization: editing a WPForms field through Quick Edit's modal.
4 // WPFormsTagger marks the rendered <form> with
5 // data-extendify-quick-edit-wpform-id and each field container with
6 // -wpform-field-id; the modal POSTs to /quick-edit/wpforms with a
7 // changes-bag that the controller shallow-merges into the stored form
8 // JSON. The contract the spec pins:
9 //
10 // 1. The field's hover bar surfaces a Quick Edit pill (Ask AI is gated
11 // off — the wpforms `source.kind` is not 'post' or null).
12 // 2. The modal pre-fills from the stored field's label / placeholder /
13 // description / required.
14 // 3. Save → reload → the new values render in the live form.
15 // 4. The untouched `select` field still surfaces both choices on
16 // reload — choices / validation are not part of the changes-bag and
17 // must survive the round-trip.
18
19 const adminBarPill = (page) => page.locator('#ext-tb-quick-edit');
20
21 const hoverBar = (page) => page.locator('.extendify-quick-edit-bar');
22
23 const enableEditMode = async (page) => {
24 await page.addInitScript(() => {
25 window.localStorage.setItem(
26 'extendify-quick-edit-mode',
27 JSON.stringify({ state: { on: true }, version: 0 }),
28 );
29 });
30 };
31
32 const dialog = (page, name: RegExp) => page.getByRole('dialog', { name });
33
34 // Prefill modals mount their labeled inputs only after an async REST GET
35 // resolves — until then they show a Spinner and keep Save disabled
36 // (disabled={saving || !data}). On a starved CI runner that GET can outlast
37 // expect()'s 5s default, racing the input assertions. Gate on Save enabled
38 // (= data loaded) before touching the inputs.
39 const waitForModalData = (modal) =>
40 expect(modal.getByRole('button', { name: /^Save$/ })).toBeEnabled({
41 timeout: 15_000,
42 });
43
44 const nameFieldContainer = (page) =>
45 page.locator('[data-extendify-quick-edit-wpform-field-id="1"]');
46
47 const selectFieldContainer = (page) =>
48 page.locator('[data-extendify-quick-edit-wpform-field-id="2"]');
49
50 test.beforeEach(async ({ requestUtils }) => {
51 await requestUtils.login();
52 });
53
54 test('WPForms field hover surfaces only the Quick Edit pill', async ({
55 page,
56 }) => {
57 await enableEditMode(page);
58 await page.goto('/');
59 await expect(adminBarPill(page)).toBeVisible({ timeout: 15_000 });
60
61 const field = nameFieldContainer(page);
62 await expect(field).toBeVisible({ timeout: 15_000 });
63 await field.scrollIntoViewIfNeeded();
64 // WPForms wraps fields in a `.wpforms-field-container` that the
65 // Playwright actionability check sees as the topmost element when
66 // hovering a child .wpforms-field. dispatchEvent fires the real DOM
67 // mouseover event the hover-bar listens for on document and skips
68 // the actionability check entirely.
69 await field.dispatchEvent('mouseover');
70
71 const bar = hoverBar(page);
72 await expect(bar).toBeVisible();
73 await expect(bar.getByRole('button', { name: /Quick Edit/ })).toBeVisible();
74 await expect(bar.getByRole('button', { name: /Ask AI/ })).toHaveCount(0);
75 });
76
77 test('WPFormsFieldModal pre-fills from stored field values', async ({
78 page,
79 }) => {
80 await enableEditMode(page);
81 await page.goto('/');
82 await expect(adminBarPill(page)).toBeVisible({ timeout: 15_000 });
83
84 const field = nameFieldContainer(page);
85 await expect(field).toBeVisible({ timeout: 15_000 });
86 await field.scrollIntoViewIfNeeded();
87 // WPForms wraps fields in a `.wpforms-field-container` that the
88 // Playwright actionability check sees as the topmost element when
89 // hovering a child .wpforms-field. dispatchEvent fires the real DOM
90 // mouseover event the hover-bar listens for on document and skips
91 // the actionability check entirely.
92 await field.dispatchEvent('mouseover');
93 await hoverBar(page)
94 .getByRole('button', { name: /Quick Edit/ })
95 .click();
96
97 const modal = dialog(page, /Edit form field/i);
98 await expect(modal).toBeVisible();
99 await waitForModalData(modal);
100
101 await expect(modal.getByLabel(/^Label$/i)).toHaveValue('Original Name Label');
102 await expect(modal.getByLabel(/^Placeholder$/i)).toHaveValue(
103 'Original placeholder',
104 );
105 await expect(modal.getByLabel(/^Description/i)).toHaveValue(
106 'Original description',
107 );
108 await expect(modal.getByLabel(/Required field/i)).toBeChecked();
109 });
110
111 test('saving field changes reloads and the new label / placeholder render', async ({
112 page,
113 }) => {
114 await enableEditMode(page);
115 await page.goto('/');
116 await expect(adminBarPill(page)).toBeVisible({ timeout: 15_000 });
117
118 const field = nameFieldContainer(page);
119 await field.scrollIntoViewIfNeeded();
120 // WPForms wraps fields in a `.wpforms-field-container` that the
121 // Playwright actionability check sees as the topmost element when
122 // hovering a child .wpforms-field. dispatchEvent fires the real DOM
123 // mouseover event the hover-bar listens for on document and skips
124 // the actionability check entirely.
125 await field.dispatchEvent('mouseover');
126 await hoverBar(page)
127 .getByRole('button', { name: /Quick Edit/ })
128 .click();
129
130 const modal = dialog(page, /Edit form field/i);
131 await expect(modal).toBeVisible();
132 await waitForModalData(modal);
133
134 await modal.getByLabel(/^Label$/i).fill('Renamed Label');
135 await modal.getByLabel(/^Placeholder$/i).fill('Renamed placeholder');
136
137 const saved = page.waitForResponse(
138 (r) =>
139 r.url().includes('/quick-edit/wpforms') &&
140 r.request().method() === 'POST' &&
141 r.status() === 200,
142 );
143 const reloaded = page.waitForLoadState('load');
144 await modal.getByRole('button', { name: /^Save$/ }).click();
145 await saved;
146 await reloaded;
147
148 const reloadedField = nameFieldContainer(page);
149 await expect(reloadedField).toBeVisible({ timeout: 15_000 });
150 await expect(reloadedField).toContainText('Renamed Label');
151 await expect(reloadedField.locator('input').first()).toHaveAttribute(
152 'placeholder',
153 'Renamed placeholder',
154 );
155 });
156
157 test('saving a field does not clobber the untouched select field choices', async ({
158 page,
159 }) => {
160 await enableEditMode(page);
161 await page.goto('/');
162 await expect(adminBarPill(page)).toBeVisible({ timeout: 15_000 });
163
164 const field = nameFieldContainer(page);
165 await field.scrollIntoViewIfNeeded();
166 // WPForms wraps fields in a `.wpforms-field-container` that the
167 // Playwright actionability check sees as the topmost element when
168 // hovering a child .wpforms-field. dispatchEvent fires the real DOM
169 // mouseover event the hover-bar listens for on document and skips
170 // the actionability check entirely.
171 await field.dispatchEvent('mouseover');
172 await hoverBar(page)
173 .getByRole('button', { name: /Quick Edit/ })
174 .click();
175
176 const modal = dialog(page, /Edit form field/i);
177 await waitForModalData(modal);
178 await modal.getByLabel(/^Label$/i).fill('Round-tripped Label');
179
180 const saved = page.waitForResponse(
181 (r) =>
182 r.url().includes('/quick-edit/wpforms') &&
183 r.request().method() === 'POST' &&
184 r.status() === 200,
185 );
186 const reloaded = page.waitForLoadState('load');
187 await modal.getByRole('button', { name: /^Save$/ }).click();
188 await saved;
189 await reloaded;
190
191 const select = selectFieldContainer(page).locator('select').first();
192 await expect(select).toBeVisible({ timeout: 15_000 });
193 await expect(select.locator('option')).toContainText(['Email', 'Phone']);
194 });
195