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 / undo.spec.ts

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

122 lines 4.6 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: Cmd+Z after a wpforms field save.
4 // WPFormsFieldModal stamps the inverse of its changes-bag into the undo
5 // entry; performUndo's `wpformsReplay` branch POSTs those originals back
6 // through /quick-edit/wpforms, which shallow-merges into the form's
7 // serialized JSON. The forward and reverse paths use the exact same
8 // endpoint shape — the controller doesn't distinguish "save" from "undo".
9
10 const adminBarPill = (page) => page.locator('#ext-tb-quick-edit');
11
12 const hoverBar = (page) => page.locator('.extendify-quick-edit-bar');
13
14 const enableEditMode = async (page) => {
15 await page.addInitScript(() => {
16 window.localStorage.setItem(
17 'extendify-quick-edit-mode',
18 JSON.stringify({ state: { on: true }, version: 0 }),
19 );
20 });
21 };
22
23 const dialog = (page, name: RegExp) => page.getByRole('dialog', { name });
24
25 // Prefill modals mount their labeled inputs only after an async REST GET
26 // resolves — until then they show a Spinner and keep Save disabled
27 // (disabled={saving || !data}). On a starved CI runner that GET can outlast
28 // expect()'s 5s default, racing the input assertions. Gate on Save enabled
29 // (= data loaded) before touching the inputs.
30 const waitForModalData = (modal) =>
31 expect(modal.getByRole('button', { name: /^Save$/ })).toBeEnabled({
32 timeout: 15_000,
33 });
34
35 // The modal save reloads the page; QE must re-mount and re-bind its Cmd+Z
36 // handler before the undo press. mount() appends #extendify-quick-edit-root
37 // just before calling attachKeyboardUndo(), so that host is the post-reload
38 // readiness signal — on a starved runner the bundle can still be loading when
39 // the press would otherwise fire (root absent → no handler, undo no-ops).
40 const waitForQuickEditReady = (page) =>
41 expect(page.locator('#extendify-quick-edit-root')).toBeAttached({
42 timeout: 15_000,
43 });
44
45 const nameFieldContainer = (page) =>
46 page.locator('[data-extendify-quick-edit-wpform-field-id="1"]');
47
48 test.beforeEach(async ({ requestUtils }) => {
49 await requestUtils.login();
50 });
51
52 test('Cmd+Z after a wpforms field save POSTs the original four props back through /quick-edit/wpforms and the rendered label + placeholder revert', async ({
53 page,
54 }) => {
55 await enableEditMode(page);
56 await page.goto('/');
57 await expect(adminBarPill(page)).toBeVisible({ timeout: 15_000 });
58
59 const field = nameFieldContainer(page);
60 await expect(field).toBeVisible({ timeout: 15_000 });
61 await field.scrollIntoViewIfNeeded();
62 // WPForms wraps fields in a `.wpforms-field-container` that the
63 // Playwright actionability check sees as the topmost element when
64 // hovering a child .wpforms-field. dispatchEvent fires the real DOM
65 // mouseover event the hover-bar listens for on document and skips
66 // the actionability check entirely.
67 await field.dispatchEvent('mouseover');
68 await hoverBar(page)
69 .getByRole('button', { name: /Quick Edit/ })
70 .click();
71
72 const modal = dialog(page, /Edit form field/i);
73 await waitForModalData(modal);
74 await modal.getByLabel(/^Label$/i).fill('Renamed via Quick Edit (undo)');
75 await modal.getByLabel(/^Placeholder$/i).fill('Renamed placeholder');
76
77 const saved = page.waitForResponse(
78 (r) =>
79 r.url().includes('/quick-edit/wpforms') &&
80 r.request().method() === 'POST' &&
81 r.status() === 200,
82 );
83 const reloaded = page.waitForLoadState('load');
84 await modal.getByRole('button', { name: /^Save$/ }).click();
85 await saved;
86 await reloaded;
87
88 const renamedField = nameFieldContainer(page);
89 await expect(renamedField).toBeVisible({ timeout: 15_000 });
90 await expect(renamedField).toContainText('Renamed via Quick Edit (undo)');
91
92 await waitForQuickEditReady(page);
93
94 // WPFormsFieldModal stamps the inverse changes-bag — only the keys
95 // touched on the forward save, paired with their pre-mutation values.
96 const undoSaved = page.waitForResponse(
97 (r) =>
98 r.url().includes('/quick-edit/wpforms') &&
99 r.request().method() === 'POST' &&
100 r.status() === 200,
101 );
102 const undoReloaded = page.waitForLoadState('load');
103 await page.locator('body').press('ControlOrMeta+z');
104 const undoRes = await undoSaved;
105 const undoBody = JSON.parse(undoRes.request().postData() || '{}');
106 expect(undoBody.form_id).toBeGreaterThan(0);
107 expect(undoBody.field_id).toBe(1);
108 expect(undoBody.changes).toEqual({
109 label: 'Original Name Label',
110 placeholder: 'Original placeholder',
111 });
112 await undoReloaded;
113
114 const restoredField = nameFieldContainer(page);
115 await expect(restoredField).toBeVisible({ timeout: 15_000 });
116 await expect(restoredField).toContainText('Original Name Label');
117 await expect(restoredField.locator('input').first()).toHaveAttribute(
118 'placeholder',
119 'Original placeholder',
120 );
121 });
122