mirror of
https://github.com/wagtail/wagtail.git
synced 2024-11-25 05:02:57 +01:00
fix: MutationObserver in dirty form check only tests direct descendants
Fixes #11142
This commit is contained in:
parent
caf9142c5d
commit
00474a6152
@ -22,6 +22,7 @@ Changelog
|
||||
* Fix: Resolve issue local development of docs when running `make livehtml` (Sage Abdullah)
|
||||
* Fix: Resolve issue with unwanted padding in chooser modal listings (Sage Abdullah)
|
||||
* Fix: Ensure form builder emails that have date or datetime fields correctly localize dates based on the configured `LANGUAGE_CODE` (Mark Niehues)
|
||||
* Fix: Ensure the Stimulus `UnsavedController` checks for nested removal/additions of inputs so that the unsaved warning shows in more valid cases when editing a page (Karthik Ayangar)
|
||||
* Docs: Add contributing development documentation on how to work with a fork of Wagtail (Nix Asteri, Dan Braghis)
|
||||
* Docs: Make sure the settings panel is listed in tabbed interface examples (Tibor Leupold)
|
||||
* Docs: Update content and page names to their US spelling instead of UK spelling (Victoria Poromon)
|
||||
|
@ -112,6 +112,96 @@ describe('UnsavedController', () => {
|
||||
expect(events['w-unsaved:add']).toHaveLength(1);
|
||||
expect(events['w-unsaved:add'][0]).toHaveProperty('detail.type', 'edits');
|
||||
});
|
||||
|
||||
it('should allow checking for when an input is removed', async () => {
|
||||
expect(events['w-unsaved:add']).toHaveLength(0);
|
||||
|
||||
await setup();
|
||||
|
||||
// setup should not fire any event
|
||||
expect(events['w-unsaved:add']).toHaveLength(0);
|
||||
|
||||
const input = document.getElementById('name');
|
||||
|
||||
input.remove();
|
||||
|
||||
await jest.runAllTimersAsync();
|
||||
|
||||
expect(events['w-unsaved:add']).toHaveLength(1);
|
||||
expect(events['w-unsaved:add'][0]).toHaveProperty('detail.type', 'edits');
|
||||
});
|
||||
|
||||
it('should ignore when non-inputs are added', async () => {
|
||||
expect(events['w-unsaved:add']).toHaveLength(0); // Ensure no initial events
|
||||
|
||||
await setup();
|
||||
|
||||
expect(events['w-unsaved:add']).toHaveLength(0); // Verify no events after setup
|
||||
|
||||
// Act (simulate the addition of a paragraph)
|
||||
const paragraph = document.createElement('p');
|
||||
paragraph.id = 'paraName';
|
||||
paragraph.textContent = 'This is a new paragraph'; // Add some content for clarity
|
||||
document.getElementsByTagName('form')[0].appendChild(paragraph); // paragraph is added
|
||||
|
||||
await jest.runAllTimersAsync();
|
||||
|
||||
// Assert (verify no events were fired)
|
||||
expect(events['w-unsaved:add']).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should fire an event when a textarea is added', async () => {
|
||||
expect(events['w-unsaved:add']).toHaveLength(0); // Ensure no initial events
|
||||
|
||||
await setup();
|
||||
|
||||
expect(events['w-unsaved:add']).toHaveLength(0); // Verify no events after setup
|
||||
|
||||
// Act (simulate adding a textarea with value)
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = 'Some initial content';
|
||||
textarea.id = 'taName';
|
||||
document.getElementsByTagName('form')[0].appendChild(textarea);
|
||||
|
||||
await jest.runAllTimersAsync(); // Allow any timers to trigger
|
||||
|
||||
// Assert (verify event was fired)
|
||||
expect(events['w-unsaved:add']).toHaveLength(1);
|
||||
expect(events['w-unsaved:add'][0]).toHaveProperty('detail.type', 'edits');
|
||||
});
|
||||
|
||||
it('should fire an event when a nested input (select) is added', async () => {
|
||||
// Arrange
|
||||
expect(events['w-unsaved:add']).toHaveLength(0); // Ensure no initial events
|
||||
|
||||
await setup();
|
||||
|
||||
expect(events['w-unsaved:add']).toHaveLength(0); // Verify no events after setup
|
||||
|
||||
// Act
|
||||
const div = document.createElement('div');
|
||||
const select = document.createElement('select');
|
||||
select.id = 'mySelect';
|
||||
|
||||
const option1 = document.createElement('option');
|
||||
option1.value = 'option1';
|
||||
option1.textContent = 'Option 1';
|
||||
select.appendChild(option1);
|
||||
|
||||
const option2 = document.createElement('option');
|
||||
option2.value = 'option2';
|
||||
option2.textContent = 'Option 2';
|
||||
select.appendChild(option2);
|
||||
|
||||
div.appendChild(select);
|
||||
document.body.getElementsByTagName('form')[0].appendChild(div);
|
||||
|
||||
await jest.runAllTimersAsync();
|
||||
|
||||
// Assert
|
||||
expect(events['w-unsaved:add']).toHaveLength(1);
|
||||
expect(events['w-unsaved:add'][0]).toHaveProperty('detail.type', 'edits');
|
||||
});
|
||||
});
|
||||
|
||||
describe('showing a confirmation message when exiting the browser tab', () => {
|
||||
|
@ -198,6 +198,17 @@ export class UnsavedController extends Controller<HTMLFormElement> {
|
||||
if (current !== previous) this.notify();
|
||||
}
|
||||
|
||||
getIsValidNode(node: Node | null) {
|
||||
if (!node || node.nodeType !== node.ELEMENT_NODE) return false;
|
||||
|
||||
const validElements = ['input', 'textarea', 'select'];
|
||||
|
||||
return (
|
||||
validElements.includes((node as Element).localName) ||
|
||||
(node as Element).querySelector(validElements.join(',')) !== null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the user of changes to the form.
|
||||
* Dispatch events to update the footer message via dispatching events.
|
||||
@ -288,15 +299,11 @@ export class UnsavedController extends Controller<HTMLFormElement> {
|
||||
detail: { initialFormData },
|
||||
});
|
||||
|
||||
const isValidInputNode = (node) =>
|
||||
node.nodeType === node.ELEMENT_NODE &&
|
||||
['INPUT', 'TEXTAREA', 'SELECT'].includes(node.tagName);
|
||||
|
||||
const observer = new MutationObserver((mutationList) => {
|
||||
const hasMutationWithValidInputNode = mutationList.some(
|
||||
(mutation) =>
|
||||
Array.from(mutation.addedNodes).some(isValidInputNode) ||
|
||||
Array.from(mutation.removedNodes).some(isValidInputNode),
|
||||
Array.from(mutation.addedNodes).some(this.getIsValidNode) ||
|
||||
Array.from(mutation.removedNodes).some(this.getIsValidNode),
|
||||
);
|
||||
|
||||
if (hasMutationWithValidInputNode) this.check();
|
||||
|
@ -35,6 +35,7 @@ depth: 1
|
||||
* Resolve issue local development of docs when running `make livehtml` (Sage Abdullah)
|
||||
* Resolve issue with unwanted padding in chooser modal listings (Sage Abdullah)
|
||||
* Ensure form builder emails that have date or datetime fields correctly localize dates based on the configured `LANGUAGE_CODE` (Mark Niehues)
|
||||
* Ensure the Stimulus `UnsavedController` checks for nested removal/additions of inputs so that the unsaved warning shows in more valid cases when editing a page (Karthik Ayangar)
|
||||
|
||||
|
||||
### Documentation
|
||||
|
Loading…
Reference in New Issue
Block a user